@jd-data-limited/easy-fm 1.1.6-alpha1 → 1.2.2-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
@@ -86,33 +81,121 @@ interface recordObject {
86
81
  recordId: number
87
82
  modId: number,
88
83
  fieldData: any,
89
- portalData: portalDataObject | undefined
84
+ portalData?: portalDataObject
85
+ }
86
+
87
+ interface fileMakerResponse {
88
+ response: any,
89
+ messages: any[]
90
90
  }
91
91
 
92
92
  interface portalDataObject {
93
93
  [key: string]: recordObject
94
94
  }
95
95
 
96
- export default class FileMakerConnection extends EventEmitter {
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 {
97
182
  private _token: any;
98
- private hostname: string;
99
- public readonly rejectUnauthroized: boolean;
100
- private databaseConDetails: hostConnectionOptions
101
- public name: string;
183
+ readonly host: FMHost;
184
+ private connection_details: databaseOptionsWithExternalSources
185
+ readonly name: string;
102
186
 
103
- constructor(conn: hostConnectionOptions, rejectUnauthorized = true) {
187
+ constructor(host: FMHost, conn: databaseOptionsWithExternalSources) {
104
188
  super()
105
- this.hostname = conn.hostname
106
- this.name = conn.database.database
107
- this.databaseConDetails = conn
108
- this.rejectUnauthroized = rejectUnauthorized
189
+ this.host = host
190
+ this.name = conn.database
191
+ this.connection_details = conn
109
192
  }
110
193
 
111
- private generateExternalSourceLogin(data: databaseOptions | loginOptionsFileMaker) {
112
- if (data.method === "filemaker") {
113
- let _data = <loginOptionsFileMaker>data
194
+ private generateExternalSourceLogin(data: databaseOptionsBase) {
195
+ if (data.credentials.method === "filemaker") {
196
+ let _data = <loginOptionsFileMaker>data.credentials
114
197
  return {
115
- database: _data.database,
198
+ database: data.database,
116
199
  username: _data.username,
117
200
  password: _data.password
118
201
  }
@@ -134,81 +217,47 @@ export default class FileMakerConnection extends EventEmitter {
134
217
  let data = await _fetch.json()
135
218
  // console.log(data)
136
219
  this._token = null
137
- this.hostname = null
138
220
  resolve()
139
221
  })
140
222
  }
141
223
 
142
224
  login() {
143
- 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
+ }
144
232
  if (this.token) throw new Error("Already logged in. Run logout() first")
145
233
 
146
- 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 {
147
238
  fetch(`${this.endpoint}/sessions`, {
148
- hostname: this.hostname,
239
+ hostname: this.host.hostname,
149
240
  port: 443,
150
241
  method: "POST",
151
- headers: {
152
- "Content-Type": "application/json",
153
- "Authorization": "Basic " + Buffer.from((<loginOptionsFileMaker>this.databaseConDetails.database).username + ":" + (<loginOptionsFileMaker>this.databaseConDetails.database).password).toString("base64")
154
- },
242
+ headers: generateAuthorizationHeaders(this.connection_details.credentials) as unknown as HeadersInit,
155
243
  body: JSON.stringify({
156
- fmDataSource: this.databaseConDetails.externalSources.map(i => {
157
- let _i = <databaseOptions>i
244
+ fmDataSource: this.connection_details.externalSources.map(i => {
245
+ let _i = <databaseOptionsBase>i
158
246
  return this.generateExternalSourceLogin(_i)
159
247
  })
160
248
  })
161
249
  }).then(async res => {
250
+ let _res = <any>(await res.json())
162
251
  if (res.status === 200) {
163
252
  this._token = res.headers.get('x-fm-data-access-token')
164
253
  resolve(this._token)
165
254
  } else {
166
- let _res = <any>(await res.json())
167
255
  reject(new FMError(_res.messages[0].code, _res.status, res))
168
256
  }
169
257
  })
170
258
  .catch(e => {
171
259
  reject(e)
172
260
  })
173
- } else if (this.databaseConDetails.database.method === "token") {
174
- this._token = (<loginOptionsToken>this.databaseConDetails.database).token
175
- resolve(this.token)
176
- } else if (this.databaseConDetails.database.method === "oauth") {
177
- fetch(`${this.endpoint}/sessions`, {
178
- method: "POST",
179
- headers: {
180
- "Content-Type": "application/json",
181
- "X-FM-Data-OAuth-RequestId": (<loginOptionsOAuth>this.databaseConDetails.database).oauth.requestId,
182
- "X-FM-Data-OAuth-Identifier": (<loginOptionsOAuth>this.databaseConDetails.database).oauth.requestIdentifier
183
- },
184
- body: JSON.stringify({
185
- fmDataSource: this.databaseConDetails.externalSources.map(i => {
186
- let _i = <databaseOptions>i
187
- return this.generateExternalSourceLogin(_i)
188
- })
189
- })
190
- })
191
- .then(res => res.json())
192
- .then(res => {
193
- let _res = <any>res
194
- this._token = _res.headers["x-fm-data-access-token"]
195
- resolve(this.token)
196
- })
197
- } else if (this.databaseConDetails.database.method === "claris") {
198
- fetch(`${this.endpoint}/sessions`, {
199
- method: "POST",
200
- headers: {
201
- "Content-Type": "application/json",
202
- "Authorization": (<loginOptionsClaris>this.databaseConDetails.database).claris.fmid,
203
- },
204
- body: "{}"
205
- })
206
- .then(res => res.json())
207
- .then(res => {
208
- let _res = <any>res
209
- this._token = _res.headers["x-fm-data-access-token"]
210
- resolve(this.token)
211
- })
212
261
  }
213
262
  })
214
263
  }
@@ -218,22 +267,22 @@ export default class FileMakerConnection extends EventEmitter {
218
267
  }
219
268
 
220
269
  get endpoint(): string {
221
- return `https://${this.hostname}/fmi/data/v2/databases/${this.name}`
270
+ return `${this.host.hostname}/fmi/data/v2/databases/${this.name}`
222
271
  }
223
272
 
224
273
  async apiRequest(url: string | Request, options: any = {}, autoRelogin = true): Promise<any> {
225
274
  if (!options.headers) options.headers = {}
226
275
  options.headers["content-type"] = options.headers["content-type"] ? options.headers["content-type"] : "application/json"
227
276
  options.headers["authorization"] = "Bearer " + this._token
228
- options.rejectUnauthorized = this.rejectUnauthroized
277
+ options.rejectUnauthorized = this.host.verify
229
278
 
230
279
  let _fetch = await fetch(url, options)
231
- let data = await _fetch.json()
232
- console.log(data.messages[0])
280
+ let data = await _fetch.json() as fileMakerResponse
281
+ // console.log(data.messages[0])
233
282
  if (data.messages[0].code == 952 && autoRelogin) {
234
283
  this._token = null
235
284
  await this.login()
236
- return await this.apiRequest(url, options,false)
285
+ return await this.apiRequest(url, options, false)
237
286
  }
238
287
  return (data as any)
239
288
  }
@@ -272,11 +321,12 @@ export default class FileMakerConnection extends EventEmitter {
272
321
  }
273
322
 
274
323
  class layout {
275
- readonly database: FileMakerConnection;
324
+ readonly database: Database;
325
+ readonly records = new layoutRecordManager(this)
276
326
  protected name: string;
277
327
  metadata: any;
278
328
 
279
- constructor(database: FileMakerConnection, name: string) {
329
+ constructor(database: Database, name: string) {
280
330
  this.database = database
281
331
  this.name = name
282
332
  }
@@ -285,29 +335,25 @@ class layout {
285
335
  return `${this.database.endpoint}/layouts/${this.name}`
286
336
  }
287
337
 
288
- getScript(script) {
289
- return new script(this, script)
290
- }
291
-
338
+ /**
339
+ * @deprecated use layout.records.create() instead
340
+ */
292
341
  createRecord(): Promise<record | Error | void> {
293
- return new Promise((resolve, reject) => {
294
- // Get the layout's metadata
295
- this.getLayoutMeta().then(layout => {
296
- let fields = {}
297
- for (let _field of this.metadata.fieldMetaData) {
298
- fields[_field.name] = ""
299
- }
300
- resolve(new record(this, -1, 0, fields))
301
- }).catch(e => {reject(e)})
302
- })
342
+ return this.records.create()
303
343
  }
304
344
 
305
- getRecord(recordId): record {
306
- 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)
307
350
  }
308
351
 
352
+ /**
353
+ * @deprecated use layout.records.find() instead
354
+ */
309
355
  newFind(): find {
310
- return new find(this)
356
+ return this.records.find()
311
357
  }
312
358
 
313
359
  runScript(script): Promise<string | FMError | Error> {
@@ -334,6 +380,11 @@ class layout {
334
380
 
335
381
  public getLayoutMeta(): Promise<layout | FMError> {
336
382
  return new Promise((resolve, reject) => {
383
+ if (this.metadata) {
384
+ resolve(this.metadata)
385
+ return
386
+ }
387
+
337
388
  this.database.apiRequest(this.endpoint).then(res => {
338
389
  this.metadata = res.response
339
390
  resolve(this)
@@ -343,6 +394,54 @@ class layout {
343
394
  }
344
395
  }
345
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
+
346
445
  class record extends EventEmitter {
347
446
  readonly layout: layout;
348
447
  public recordId: number;
@@ -376,18 +475,46 @@ class record extends EventEmitter {
376
475
 
377
476
  private processPortalData(portalData): void {
378
477
  for (let item of Object.keys(portalData)) {
379
- this.portals.push(new portal(this, item, portalData[item].map(item => {
380
- let fieldData = item
381
- delete fieldData.recordId
382
- delete fieldData.modId
383
- return new portalItem(this, item.recordId, item.modId, fieldData)
384
- })))
478
+ let _portal = new portal(this, item)
479
+ _portal.records = portalData[item].map(item => {
480
+ let fieldData = item;
481
+ delete fieldData.recordId;
482
+ delete fieldData.modId;
483
+ return new portalItem(this, _portal, item.recordId, item.modId, fieldData);
484
+ })
485
+ this.portals.push();
385
486
  }
386
487
  }
387
488
 
388
489
  processFieldData(fieldData): field[] {
389
490
  return Object.keys(fieldData).map(item => {
390
- return new field(this, item, fieldData[item])
491
+ let _field = new field(this, item, fieldData[item])
492
+ if (!!fieldData[item]) {
493
+ if (_field.metadata.result === "timeStamp") {
494
+ // @ts-ignore
495
+ let date = moment.default(fieldData[item], this.layout.database.host.metadata.productInfo.timeStampFormat)
496
+ .utcOffset(this.layout.database.host.timezoneOffset, true)
497
+ .local()
498
+ _field.set(date.toDate())
499
+ _field.edited = false
500
+
501
+ } else if (_field.metadata.result === "time") {
502
+ // @ts-ignore
503
+ let date = moment.default(fieldData[item], this.layout.database.host.metadata.productInfo.timeFormat)
504
+ .utcOffset(this.layout.database.host.timezoneOffset, true)
505
+ .local()
506
+ _field.set(date.toDate())
507
+ _field.edited = false
508
+ } else if (_field.metadata.result === "date") {
509
+ // @ts-ignore
510
+ let date = moment.default(fieldData[item], this.layout.database.host.metadata.productInfo.dateFormat)
511
+ .utcOffset(this.layout.database.host.timezoneOffset, true)
512
+ .local()
513
+ _field.set(date.toDate())
514
+ _field.edited = false
515
+ }
516
+ }
517
+ return _field
391
518
  })
392
519
  }
393
520
 
@@ -441,7 +568,6 @@ class record extends EventEmitter {
441
568
  if (typeof res.response.scriptError !== "undefined" && res.response.scriptError !== '0') {
442
569
  reject(new FMError(res.response.scriptError, res.status, res))
443
570
  } else if (res.messages[0].code === "0") {
444
- console.log(res)
445
571
  this.recordId = parseInt(res.response.recordId)
446
572
  this.modId = parseInt(res.response.modId)
447
573
  resolve(this)
@@ -456,17 +582,16 @@ class record extends EventEmitter {
456
582
  return
457
583
  }
458
584
 
459
- for (let item of Object.keys(extraBody)) data[item] = extraBody[item]
585
+ for (let item of Object.keys(data)) extraBody[item] = data[item]
460
586
  this.layout.database.apiRequest(this.endpoint, {
461
587
  port: 443,
462
588
  method: "PATCH",
463
- body: JSON.stringify(data)
589
+ body: JSON.stringify(extraBody)
464
590
  })
465
591
  .then(res => {
466
592
  if (typeof res.response.scriptError !== "undefined" && res.response.scriptError !== '0') {
467
593
  reject(new FMError(res.response.scriptError, res.status, res))
468
594
  } else if (res.messages[0].code === "0") {
469
- console.log(res)
470
595
  this.modId = res.response.modId
471
596
  this.emit("saved")
472
597
  resolve(this)
@@ -487,7 +612,7 @@ class record extends EventEmitter {
487
612
  getPortal(portal) {
488
613
  return this.portals.find(p => p.name === portal)
489
614
  }
490
-
615
+
491
616
  delete(): Promise<void> {
492
617
  return new Promise(async (resolve, reject) => {
493
618
  this.layout.database.apiRequest(this.endpoint, {
@@ -543,7 +668,26 @@ class record extends EventEmitter {
543
668
  ): recordObject {
544
669
  let fields_processed = {}
545
670
  for (let field of this.fields.filter(field => filter(field))) {
546
- fields_processed[field.id] = field.value
671
+ let value = field.value
672
+ if (value instanceof Date) {
673
+ // @ts-ignore
674
+ let _value = moment.default(value)
675
+ .utcOffset(this.layout.database.host.timezoneOffset)
676
+
677
+ // @ts-ignore
678
+
679
+ switch (field.metadata.result) {
680
+ case "time":
681
+ value = _value.format(this.layout.database.host.metadata.productInfo.timeFormat.replace("dd", "DD"))
682
+ break
683
+ case "date":
684
+ value = _value.format(this.layout.database.host.metadata.productInfo.dateFormat.replace("dd", "DD"))
685
+ break
686
+ default:
687
+ value = _value.format(this.layout.database.host.metadata.productInfo.timeStampFormat.replace("dd", "DD"))
688
+ }
689
+ }
690
+ fields_processed[field.id] = value
547
691
  }
548
692
  let obj = {
549
693
  "recordId": this.recordId,
@@ -568,33 +712,93 @@ class record extends EventEmitter {
568
712
  class field {
569
713
  record: record;
570
714
  id: number;
571
- value: string | number;
715
+ private _value: string | number | Date;
572
716
  edited: boolean;
573
717
 
574
718
  constructor(record, id, contents) {
575
719
  this.record = record
576
720
  this.id = id
577
- this.value = contents
721
+ this._value = contents
578
722
  this.edited = false
579
723
  }
580
724
 
581
- set(content) {
582
- if (this.dataType === "container") throw "Cannot set container value using set(). Use upload() instead."
583
- this.value = content
725
+ set(content: string | number | Date) {
726
+ if (this.metadata.result === "container") throw "Cannot set container value using set(). Use upload() instead."
727
+ if (
728
+ (this.metadata.result === "timeStamp" ||
729
+ this.metadata.result === "date" ||
730
+ this.metadata.result === "time")
731
+ && !(content instanceof Date) && !!content
732
+ ) {
733
+ throw "Value was not an instance of Date: " + content
734
+ }
735
+ this._value = content
584
736
  this.edited = true
585
737
  }
586
738
 
587
- get dataType() {
588
- if (!this.record.layout.metadata) return "unknown"
739
+ get metadata(): fieldMetaData {
740
+ if (!this.record.layout.metadata) {
741
+ // Default to a regular text field
742
+ return {
743
+ name: this.id.toString(),
744
+ type: 'normal',
745
+ displayType: 'editText',
746
+ result: 'text',
747
+ global: false,
748
+ autoEnter: true,
749
+ fourDigitYear: false,
750
+ maxRepeat: 1,
751
+ maxCharacters: 0,
752
+ notEmpty: false,
753
+ numeric: false,
754
+ timeOfDay: false,
755
+ repetitionStart: 1,
756
+ repetitionEnd: 1
757
+ } as fieldMetaData
758
+ }
589
759
  if (this.record instanceof portalItem) {
590
- return this.record.layout.metadata.portalMetaData.find(i => i.name === this.id).result || "unknown"
760
+ return this.record.layout.metadata.portalMetaData[this.record.portal.name || "portal not attached"].find(i => i.name === this.id) as fieldMetaData || {
761
+ name: this.id.toString(),
762
+ type: 'normal',
763
+ displayType: 'editText',
764
+ result: 'text',
765
+ global: false,
766
+ autoEnter: true,
767
+ fourDigitYear: false,
768
+ maxRepeat: 1,
769
+ maxCharacters: 0,
770
+ notEmpty: false,
771
+ numeric: false,
772
+ timeOfDay: false,
773
+ repetitionStart: 1,
774
+ repetitionEnd: 1
775
+ } as fieldMetaData
591
776
  } else {
592
- return this.record.layout.metadata.fieldMetaData.find(i => i.name === this.id).result || "unknown"
777
+ return this.record.layout.metadata.fieldMetaData.find(i => i.name === this.id) as fieldMetaData || {
778
+ name: this.id.toString(),
779
+ type: 'normal',
780
+ displayType: 'editText',
781
+ result: 'text',
782
+ global: false,
783
+ autoEnter: true,
784
+ fourDigitYear: false,
785
+ maxRepeat: 1,
786
+ maxCharacters: 0,
787
+ notEmpty: false,
788
+ numeric: false,
789
+ timeOfDay: false,
790
+ repetitionStart: 1,
791
+ repetitionEnd: 1
792
+ } as fieldMetaData
593
793
  }
594
794
  }
595
795
 
796
+ get value() {
797
+ return this._value
798
+ }
799
+
596
800
  upload(buffer: Buffer, filename: string, mime: string): Promise<void | FMError> {
597
- if (this.dataType !== "container") throw "Cannot upload a file to the field; " + this.id + " (not a container field)"
801
+ if (this.metadata.result !== "container") throw "Cannot upload a file to the field; " + this.id + " (not a container field)"
598
802
  return new Promise(async (resolve, reject) => {
599
803
  let form = new FormData()
600
804
  form.append("upload", new File([buffer], filename, {type: mime}))
@@ -632,28 +836,23 @@ class portal {
632
836
  readonly name: string;
633
837
  public records: portalItem[];
634
838
 
635
- constructor(record: record, name: string, records: portalItem[] = []) {
839
+ constructor(record: record, name: string) {
636
840
  this.record = record
637
841
  this.name = name
638
- this.records = records
639
- for (let item of this.records) item.attachPortal(this)
640
842
  }
641
843
  }
642
844
 
643
845
  class portalItem extends record {
644
- protected portal: portal;
846
+ readonly portal: portal;
645
847
 
646
- constructor(record, recordId, modId = recordId, fieldData = {}) {
848
+ constructor(record, portal, recordId, modId = recordId, fieldData = {}) {
647
849
  super(record.layout, recordId, modId, fieldData);
850
+ this.portal = portal
648
851
  record.on("saved", () => {
649
852
  this.emit("saved")
650
853
  })
651
854
  }
652
855
 
653
- attachPortal(portal) {
654
- this.portal = portal
655
- }
656
-
657
856
  commit(extraBody = {fieldData: undefined}) {
658
857
  return this.portal.record.commit(extraBody)
659
858
  }
@@ -734,24 +933,27 @@ class recordGetRange extends recordGetOperation {
734
933
  return "?" + params.join("&")
735
934
  }
736
935
 
737
- run() {
936
+ run(): Promise<record[]> {
738
937
  return new Promise((resolve, reject) => {
739
938
  // console.log(this.#toObject())
740
- this.layout.database.apiRequest(`${this.layout.endpoint}/records${this.generateQueryParams()}`, {
741
- method: "GET"
742
- }).then(async res => {
743
- // // console.log(res)
744
- if (res.messages[0].code === "0") {
745
- // console.log("RESOLVING")
746
- if (!this.layout.metadata) await this.layout.getLayoutMeta()
747
- let data = res.response.data.map(item => {
748
- return new record(this.layout, item.recordId, item.modId, item.fieldData, item.portalData)
749
- })
750
- resolve(data)
751
- } else {
752
- reject(new FMError(res.messages[0].code, res.status, res))
753
- }
939
+ this.layout.getLayoutMeta().then(() => {
940
+ return this.layout.database.apiRequest(`${this.layout.endpoint}/records${this.generateQueryParams()}`, {
941
+ method: "GET"
942
+ })
754
943
  })
944
+ .then(async res => {
945
+ // // console.log(res)
946
+ if (res.messages[0].code === "0") {
947
+ // console.log("RESOLVING")
948
+ if (!this.layout.metadata) await this.layout.getLayoutMeta()
949
+ let data = res.response.data.map(item => {
950
+ return new record(this.layout, item.recordId, item.modId, item.fieldData, item.portalData)
951
+ })
952
+ resolve(data)
953
+ } else {
954
+ reject(new FMError(res.messages[0].code, res.status, res))
955
+ }
956
+ })
755
957
  .catch(e => {
756
958
  reject(e)
757
959
  })
@@ -800,26 +1002,30 @@ class find extends recordGetOperation {
800
1002
  return this.run()
801
1003
  }
802
1004
 
803
- run() {
1005
+ run(): Promise<record[]> {
804
1006
  return new Promise((resolve, reject) => {
805
1007
  // console.log(this.#toObject())
806
- this.layout.database.apiRequest(`${this.layout.endpoint}/_find`, {
807
- port: 443,
808
- method: "POST",
809
- body: JSON.stringify(this.toObject())
810
- }).then(async res => {
811
- // // console.log(res)
812
- if (res.messages[0].code === "0") {
813
- // console.log("RESOLVING")
814
- if (!this.layout.metadata) await this.layout.getLayoutMeta()
815
- let data = res.response.data.map(item => {
816
- return new record(this.layout, item.recordId, item.modId, item.fieldData, item.portalData)
1008
+ this.layout.getLayoutMeta()
1009
+ .then(() => {
1010
+ return this.layout.database.apiRequest(`${this.layout.endpoint}/_find`, {
1011
+ port: 443,
1012
+ method: "POST",
1013
+ body: JSON.stringify(this.toObject())
817
1014
  })
818
- resolve(data)
819
- } else {
820
- reject(new FMError(res.messages[0].code, res.status, res))
821
- }
822
- })
1015
+ })
1016
+ .then(async res => {
1017
+ // // console.log(res)
1018
+ if (res.messages[0].code === "0") {
1019
+ // console.log("RESOLVING")
1020
+ if (!this.layout.metadata) await this.layout.getLayoutMeta()
1021
+ let data = res.response.data.map(item => {
1022
+ return new record(this.layout, item.recordId, item.modId, item.fieldData, item.portalData)
1023
+ })
1024
+ resolve(data)
1025
+ } else {
1026
+ reject(new FMError(res.messages[0].code, res.status, res))
1027
+ }
1028
+ })
823
1029
  .catch(e => {
824
1030
  reject(e)
825
1031
  })
@@ -848,4 +1054,36 @@ export class FMError extends Error {
848
1054
  //
849
1055
  // module.exports = {
850
1056
  // default: {FileMakerConnection}
851
- // }
1057
+ // }
1058
+
1059
+ interface AuthorizationHeaders {
1060
+ "Content-Type": "application/json"
1061
+ Authorization: string
1062
+ }
1063
+
1064
+ interface AuthorizationHeadersOAuth {
1065
+ "Content-Type": "application/json"
1066
+ "X-FM-Data-OAuth-RequestId": string,
1067
+ "X-FM-Data-OAuth-Identifier": string
1068
+ }
1069
+
1070
+ function generateAuthorizationHeaders(credentials: loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris): AuthorizationHeaders | AuthorizationHeadersOAuth {
1071
+ switch (credentials.method) {
1072
+ case "filemaker":
1073
+ return {
1074
+ "Content-Type": "application/json",
1075
+ "Authorization": "Basic " + Buffer.from((<loginOptionsFileMaker>credentials).username + ":" + (<loginOptionsFileMaker>credentials).password).toString("base64")
1076
+ } as AuthorizationHeaders
1077
+ case "claris":
1078
+ return {
1079
+ "Content-Type": "application/json",
1080
+ "Authorization": (<loginOptionsClaris>credentials).claris.fmid,
1081
+ } as AuthorizationHeaders
1082
+ case "oauth":
1083
+ return {
1084
+ "Content-Type": "application/json",
1085
+ "X-FM-Data-OAuth-RequestId": (<loginOptionsOAuth>credentials).oauth.requestId,
1086
+ "X-FM-Data-OAuth-Identifier": (<loginOptionsOAuth>credentials).oauth.requestIdentifier
1087
+ } as AuthorizationHeadersOAuth
1088
+ }
1089
+ }