@jd-data-limited/easy-fm 0.0.7 → 0.0.8

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
@@ -1,585 +1,584 @@
1
- /*
2
- * Copyright (c) 2022. See LICENSE file for more information
3
- */
4
-
5
- // const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
6
- import fetch, {
7
- Blob,
8
- blobFrom,
9
- blobFromSync,
10
- File,
11
- fileFrom,
12
- fileFromSync,
13
- FormData,
14
- Headers,
15
- Request,
16
- Response
17
- } from 'node-fetch';
18
- import { EventEmitter } from "events";
19
- // @ts-ignore
20
- import fs from "fs"
21
- // @ts-ignore
22
- import path from "path"
23
- // @ts-ignore
24
- import { fileURLToPath } from "url"
25
- import * as https from "https";
26
- // import * as btoa from "btoa";
27
-
28
- // @ts-ignore
29
- const errs = JSON.parse(fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), 'fmErrors.json')).toString())
30
-
31
- interface Script {
32
- name: string
33
- parameter: string
34
- }
35
-
36
- interface recordObject {
37
- recordId: number
38
- modId: number,
39
- fieldData: any
40
- }
41
-
42
- export default class FileMakerConnection {
43
- public hostname: string;
44
- public database: string;
45
- public token: any;
46
- private username: any;
47
- private password: any;
48
- private rejectUnauthroized: boolean;
49
-
50
- constructor() {
51
- }
52
-
53
- get endpoint(): string {
54
- return `https://${this.hostname}/fmi/data/v2/databases/${encodeURI(this.database)}`
55
- }
56
-
57
- async login(hostname, database, username, password, rejectUnauthorized = true) {
58
- if (this.token) throw new Error("Already logged in. Run logout() first")
59
-
60
- this.username = username
61
- this.password = password
62
- this.hostname = hostname
63
- this.database = database
64
- this.rejectUnauthroized = rejectUnauthorized
65
-
66
- return await this.createSession()
67
- }
68
-
69
- logout(): Promise<void> {
70
- return new Promise(async (resolve, reject) => {
71
- if (!this.token) reject(new Error("Not logged in"))
72
-
73
- let _fetch = await fetch(`${this.endpoint}/sessions/${this.token}`, {
74
- method: "DELETE",
75
- headers: {
76
- "content-type": "application/json"
77
- }
78
- })
79
- let data = await _fetch.json()
80
- // console.log(data)
81
- this.token = null
82
- this.database = null
83
- this.hostname = null
84
- resolve()
85
- })
86
- }
87
-
88
- importSession(hostname, database, token, rejectUnauthorized = true) {
89
- this.token = token
90
- this.database = database
91
- this.hostname = hostname
92
- this.rejectUnauthroized = rejectUnauthorized
93
- }
94
-
95
- private createSession(): Promise<string> {
96
- return new Promise(async (resolve, reject) => {
97
- fetch(`${this.endpoint}/sessions`, {
98
- hostname: this.hostname,
99
- port: 443,
100
- method: "POST",
101
- headers: {
102
- "Content-Type": "application/json",
103
- "Authorization": "Basic " + Buffer.from(this.username + ":" + this.password).toString("base64")
104
- }
105
- }).then(res => {
106
- console.log(res)
107
- return res.json()
108
- })
109
- .then(res => {
110
- let _res = res as any
111
- if (_res.messages[0].code === "0") {
112
- this.token = _res.response.token
113
- console.log(_res)
114
- resolve(this.token)
115
- } else {
116
- reject(new FMError(_res.messages[0].code, _res.status, res))
117
- }
118
- })
119
- .catch(e => {
120
- reject(e)
121
- })
122
- })
123
- }
124
-
125
- getLayout(name): layout {
126
- return new layout(this, name)
127
- }
128
-
129
- async apiRequest(url: string | Request, options: any = {}): Promise<any> {
130
- if (!options.headers) options.headers = {}
131
- options.headers["content-type"] = options.headers["content-type"] ? options.headers["content-type"] : "application/json"
132
- options.headers["authorization"] = "Bearer " + this.token
133
- options.rejectUnauthorized = this.rejectUnauthroized
134
-
135
- let _fetch = await fetch(url, options)
136
- let data = await _fetch.json()
137
- return (data as any)
138
- }
139
-
140
- setGlobals(globalFields): Promise<void> {
141
- // console.log({globalFields})
142
- return new Promise((resolve, reject) => {
143
- this.apiRequest(`${this.endpoint}/globals`, {
144
- method: "PATCH",
145
- body: JSON.stringify({globalFields})
146
- }).then(res => {
147
- // console.log(res)
148
- if (res.messages[0].code === "0") {
149
- resolve()
150
- } else {
151
- reject(new FMError(res.messages[0].code, res.status, res))
152
- }
153
- })
154
- .catch(e => {
155
- reject(e)
156
- })
157
- })
158
- }
159
-
160
- script(name, parameter): Script {
161
- return ({name, parameter} as Script)
162
- }
163
- }
164
-
165
- class layout {
166
- readonly conn: FileMakerConnection;
167
- protected name: string;
168
- metadata: any;
169
- constructor(conn: FileMakerConnection, name: string) {
170
- this.conn = conn
171
- this.name = name
172
- }
173
-
174
- get endpoint() {
175
- return `${this.conn.endpoint}/layouts/${this.name}`
176
- }
177
-
178
- getScript(script) {
179
- return new script(this, script)
180
- }
181
-
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))
191
- })
192
- })
193
- }
194
-
195
- getRecord(recordId): record {
196
- return new record(this, recordId)
197
- }
198
-
199
- newFind(): find {
200
- return new find(this)
201
- }
202
-
203
- runScript(script): Promise<string | FMError | Error> {
204
- return new Promise(async (resolve, reject) => {
205
- let url = `${this.endpoint}/script/${encodeURI(script.name)}`
206
- if (script.parameter) url += "?" + encodeURI(script.parameter)
207
- this.conn.apiRequest(url, {
208
- port: 443,
209
- method: "GET"
210
- })
211
- .then(res => {
212
- if (res.messages[0].code === "0") {
213
- resolve(res.response)
214
- } else {
215
- // console.log(res)
216
- reject(new FMError(res.messages[0].code, res.status, res))
217
- }
218
- })
219
- .catch(e => {
220
- reject(e)
221
- })
222
- })
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
- }
234
- }
235
-
236
- class record extends EventEmitter {
237
- readonly layout: layout;
238
- public recordId: number;
239
- modId: number;
240
- fields: field[];
241
- readonly portals: portal[];
242
- private portalData: any[];
243
-
244
- constructor(layout, recordId, modId = recordId, fieldData = {}, portalData = null) {
245
- super();
246
- this.layout = layout
247
- this.recordId = recordId
248
- this.modId = modId
249
- this.fields = this.processFieldData(fieldData)
250
- this.portals = []
251
- if (portalData) {
252
- this.processPortalData(portalData)
253
- }
254
- this.on("saved", () => {
255
- for (let field of this.fields) field.edited = false
256
- })
257
- }
258
-
259
- get endpoint(): string {
260
- return `${this.layout.endpoint}/records/${this.recordId}`
261
- }
262
-
263
- get edited(): boolean {
264
- return !!this.fields.find(i => i.edited)
265
- }
266
-
267
- private processPortalData(portalData): void {
268
- for (let item of Object.keys(portalData)) {
269
- this.portals.push(new portal(this, item, portalData[item].map(item => {
270
- let fieldData = item
271
- delete fieldData.recordId
272
- delete fieldData.modId
273
- return new portalItem(this, item.recordId, item.modId, fieldData)
274
- })))
275
- }
276
- }
277
-
278
- processFieldData(fieldData): field[] {
279
- return Object.keys(fieldData).map(item => {
280
- return new field(this, item, fieldData[item])
281
- })
282
- }
283
-
284
- get() {
285
- return new Promise(async (resolve, reject) => {
286
- if (!this.layout.metadata) await this.layout.getLayoutMeta()
287
-
288
- this.layout.conn.apiRequest(this.endpoint, {
289
- port: 443,
290
- method: "GET"
291
- })
292
- .then(res => {
293
- if (res.messages[0].code === "0") {
294
- // console.log(res, res.response.data)
295
- this.modId = res.response.data[0].modId
296
- this.fields = this.processFieldData(res.response.data[0].fieldData)
297
- this.portalData = []
298
- if (res.response.data[0].portalData) this.processPortalData(res.response.data[0].portalData)
299
- resolve(this)
300
- } else {
301
- reject(new FMError(res.messages[0].code, res.status, res))
302
- }
303
- })
304
- .catch(e => {
305
- reject(e)
306
- })
307
- })
308
- }
309
-
310
- commit(extraBody = {}): Promise<record | FMError> {
311
- return new Promise(async (resolve, reject) => {
312
- let data = this.toObject()
313
- delete data.recordId
314
- delete data.modId
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
-
341
- for (let item of Object.keys(extraBody)) data[item] = extraBody[item]
342
- this.layout.conn.apiRequest(this.endpoint, {
343
- port: 443,
344
- method: "PATCH",
345
- body: JSON.stringify(data)
346
- })
347
- .then(res => {
348
- if (res.messages[0].code === "0") {
349
- this.modId = res.response.modId
350
- this.emit("saved")
351
- resolve(this)
352
- } else {
353
- reject(new FMError(res.messages[0].code, res.status, res))
354
- }
355
- })
356
- .catch(e => {
357
- reject(e)
358
- })
359
- })
360
- }
361
-
362
- getField(field) {
363
- return this.fields.find(_field => _field.id === field)
364
- }
365
-
366
- getPortal(portal) {
367
- return this.portals.find(p => p.name === portal)
368
- }
369
-
370
- toObject(filter = (a) => a.edited,
371
- portalFilter = (a) => a.records.find(record => record.edited),
372
- portalRowFilter = (a) => a.edited,
373
- portalFieldFilter = (a) => a.edited
374
- ): recordObject {
375
- let fields_processed = {}
376
- for (let field of this.fields.filter(field => filter(field))) {
377
- fields_processed[field.id] = field.value
378
- }
379
- let obj = {
380
- "recordId": this.recordId,
381
- "modId": this.modId,
382
- "fieldData": fields_processed
383
- }
384
-
385
- // Check if there's been any edited portal information
386
- let portals = this.portals.filter(a => portalFilter(a))
387
- if (portals) {
388
- obj["portalData"] = {}
389
- for (let portal of portals) {
390
- obj["portalData"][portal.name] = portal.records.filter(a => portalRowFilter(a)).map(record => {
391
- return record.toObject(portalFieldFilter)
392
- })
393
- }
394
- }
395
- return obj
396
- }
397
- }
398
-
399
- class field {
400
- record: record;
401
- id: number;
402
- value: string | number;
403
- edited: boolean;
404
- constructor(record, id, contents) {
405
- this.record = record
406
- this.id = id
407
- this.value = contents
408
- this.edited = false
409
- }
410
-
411
- set(content) {
412
- if (this.dataType === "container") throw "Cannot set container value using set(). Use upload() instead."
413
- this.value = content
414
- this.edited = true
415
- }
416
-
417
- get dataType() {
418
- if (!this.record.layout.metadata) return "unknown"
419
- if (this.record instanceof portalItem) {
420
- return this.record.layout.metadata.portalMetaData.find(i => i.name === this.id).result || "unknown"
421
- } else {
422
- return this.record.layout.metadata.fieldMetaData.find(i => i.name === this.id).result || "unknown"
423
- }
424
- }
425
-
426
- upload(buffer: Buffer, filename: string, mime: string): Promise<void | FMError> {
427
- if (this.dataType !== "container") throw "Cannot upload a file to the field; " + this.id + " (not a container field)"
428
- return new Promise(async (resolve, reject) => {
429
- let form = new FormData()
430
- form.append("upload", new File([buffer], filename, {type: mime}))
431
-
432
- let _fetch = await fetch(`${this.record.endpoint}/containers/${this.id}/1`, {
433
- method: "POST",
434
- headers: {"Authorization": "Bearer " + this.record.layout.conn.token},
435
- body: form
436
- }).then(res => res.json())
437
- .then(data => {
438
- let _res = data as any
439
- if (_res.messages[0].code === "0") {
440
- resolve()
441
- } else {
442
- reject(new FMError(_res.messages[0].code, _res.status, data))
443
- }
444
- })
445
- .catch(e => {reject(e)})
446
- })
447
- }
448
- download() {
449
- return new Promise((resolve, reject) => {
450
- https.get(this.value.toString(), (res) => {
451
- resolve(res)
452
- })
453
- })
454
- }
455
- }
456
-
457
- class portal {
458
- readonly record: record;
459
- readonly name: string;
460
- public records: portalItem[];
461
-
462
- constructor(record: record, name: string, records: portalItem[] = []) {
463
- this.record = record
464
- this.name = name
465
- this.records = records
466
- for (let item of this.records) item.attachPortal(this)
467
- }
468
- }
469
-
470
- class portalItem extends record {
471
- protected portal: portal;
472
-
473
- constructor(record, recordId, modId = recordId, fieldData = {}) {
474
- super(record.layout, recordId, modId, fieldData);
475
- record.on("saved", () => {
476
- this.emit("saved")
477
- })
478
- }
479
-
480
- attachPortal(portal) {
481
- this.portal = portal
482
- }
483
-
484
- commit(extraBody = {}) {
485
- return this.portal.record.commit(extraBody)
486
- }
487
-
488
- toObject(fieldFilter): any {
489
- super.toObject()
490
- let res = {
491
- recordId: this.recordId, modId: this.modId
492
- } as recordObject
493
- for (let field of this.fields.filter(a => fieldFilter(a))) res[field.id] = field.value
494
- // console.log(res)
495
- return res
496
- }
497
- }
498
-
499
- class find {
500
- protected layout: layout
501
- protected queries: object[]
502
- protected scripts: object
503
- protected sort: object[]
504
-
505
- constructor(layout) {
506
- this.layout = layout
507
- this.queries = []
508
- this.scripts = {
509
- "script": null, // Runs after everything
510
- "script.prerequeset": null, // Runs before the request
511
- "script.presort": null // Runs before the sort
512
- }
513
- this.sort = []
514
- }
515
-
516
- private toObject() {
517
- let out = {query: this.queries, sort: undefined}
518
- if (this.sort.length !== 0) out.sort = this.sort
519
-
520
- for (let item of Object.keys(this.scripts)) {
521
- if (this.scripts[item]) {
522
- out[item] = this.scripts[item].name
523
- if (this.scripts[item].parameter) out[item + ".param"] = this.scripts[item].parameter
524
- }
525
- }
526
-
527
- return out
528
- }
529
-
530
- addRequests(...requests) {
531
- for (let item of requests) this.queries.push(item)
532
- return this
533
- }
534
-
535
- addSort(fieldName, sortOrder) {
536
- this.sort.push({fieldName, sortOrder})
537
- return this
538
- }
539
-
540
- find() {
541
- return new Promise((resolve, reject) => {
542
- // console.log(this.#toObject())
543
- this.layout.conn.apiRequest(`${this.layout.endpoint}/_find`, {
544
- port: 443,
545
- method: "POST",
546
- body: JSON.stringify(this.toObject())
547
- }).then(async res => {
548
- // // console.log(res)
549
- if (res.messages[0].code === "0") {
550
- // console.log("RESOLVING")
551
- if (!this.layout.metadata) await this.layout.getLayoutMeta()
552
- let data = res.response.data.map(item => {
553
- return new record(this.layout, item.recordId, item.modId, item.fieldData, item.portalData)
554
- })
555
- resolve(data)
556
- } else {
557
- reject(new FMError(res.messages[0].code, res.status, res))
558
- }
559
- })
560
- .catch(e => {
561
- reject(e)
562
- })
563
- })
564
- }
565
- }
566
-
567
- export class FMError extends Error {
568
- readonly httpStatus: number
569
- readonly res: any
570
- readonly code: number
571
- readonly message: string
572
-
573
- constructor(code, httpStatus, res) {
574
- super(code);
575
- this.httpStatus = httpStatus
576
- this.res = res
577
- this.code = typeof code === "string" ? parseInt(code) : code
578
- this.message = errs.find(err => err.e === this.code) || "Unknown error"
579
- }
580
- }
581
-
582
- //
583
- // module.exports = {
584
- // default: {FileMakerConnection}
1
+ /*
2
+ * Copyright (c) 2022. See LICENSE file for more information
3
+ */
4
+
5
+ // const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
6
+ import fetch, {
7
+ Blob,
8
+ blobFrom,
9
+ blobFromSync,
10
+ File,
11
+ fileFrom,
12
+ fileFromSync,
13
+ FormData,
14
+ Headers,
15
+ Request,
16
+ Response
17
+ } from 'node-fetch';
18
+ import { EventEmitter } from "events";
19
+ // @ts-ignore
20
+ import fs from "fs"
21
+ // @ts-ignore
22
+ import path from "path"
23
+ // @ts-ignore
24
+ import { fileURLToPath } from "url"
25
+ import * as https from "https";
26
+ // import * as btoa from "btoa";
27
+
28
+ // @ts-ignore
29
+ const errs = JSON.parse(fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), 'fmErrors.json')).toString())
30
+
31
+ interface Script {
32
+ name: string
33
+ parameter: string
34
+ }
35
+
36
+ interface recordObject {
37
+ recordId: number
38
+ modId: number,
39
+ fieldData: any
40
+ }
41
+
42
+ export default class FileMakerConnection {
43
+ public hostname: string;
44
+ public database: string;
45
+ public token: any;
46
+ private username: any;
47
+ private password: any;
48
+ private rejectUnauthroized: boolean;
49
+
50
+ constructor() {}
51
+
52
+ get endpoint(): string {
53
+ return `https://${this.hostname}/fmi/data/v2/databases/${encodeURI(this.database)}`
54
+ }
55
+
56
+ async login(hostname, database, username, password, rejectUnauthorized = true) {
57
+ if (this.token) throw new Error("Already logged in. Run logout() first")
58
+
59
+ this.username = username
60
+ this.password = password
61
+ this.hostname = hostname
62
+ this.database = database
63
+ this.rejectUnauthroized = rejectUnauthorized
64
+
65
+ return await this.createSession()
66
+ }
67
+
68
+ logout(): Promise<void> {
69
+ return new Promise(async (resolve, reject) => {
70
+ if (!this.token) reject(new Error("Not logged in"))
71
+
72
+ let _fetch = await fetch(`${this.endpoint}/sessions/${this.token}`, {
73
+ method: "DELETE",
74
+ headers: {
75
+ "content-type": "application/json"
76
+ }
77
+ })
78
+ let data = await _fetch.json()
79
+ // console.log(data)
80
+ this.token = null
81
+ this.database = null
82
+ this.hostname = null
83
+ resolve()
84
+ })
85
+ }
86
+
87
+ importSession(hostname, database, token, rejectUnauthorized = true) {
88
+ this.token = token
89
+ this.database = database
90
+ this.hostname = hostname
91
+ this.rejectUnauthroized = rejectUnauthorized
92
+ }
93
+
94
+ private createSession(): Promise<string> {
95
+ return new Promise(async (resolve, reject) => {
96
+ fetch(`${this.endpoint}/sessions`, {
97
+ hostname: this.hostname,
98
+ port: 443,
99
+ method: "POST",
100
+ headers: {
101
+ "Content-Type": "application/json",
102
+ "Authorization": "Basic " + Buffer.from(this.username + ":" + this.password).toString("base64")
103
+ }
104
+ }).then(res => {
105
+ console.log(res)
106
+ return res.json()
107
+ })
108
+ .then(res => {
109
+ let _res = res as any
110
+ if (_res.messages[0].code === "0") {
111
+ this.token = _res.response.token
112
+ console.log(_res)
113
+ resolve(this.token)
114
+ } else {
115
+ reject(new FMError(_res.messages[0].code, _res.status, res))
116
+ }
117
+ })
118
+ .catch(e => {
119
+ reject(e)
120
+ })
121
+ })
122
+ }
123
+
124
+ getLayout(name): layout {
125
+ return new layout(this, name)
126
+ }
127
+
128
+ async apiRequest(url: string | Request, options: any = {}): Promise<any> {
129
+ if (!options.headers) options.headers = {}
130
+ options.headers["content-type"] = options.headers["content-type"] ? options.headers["content-type"] : "application/json"
131
+ options.headers["authorization"] = "Bearer " + this.token
132
+ options.rejectUnauthorized = this.rejectUnauthroized
133
+
134
+ let _fetch = await fetch(url, options)
135
+ let data = await _fetch.json()
136
+ return (data as any)
137
+ }
138
+
139
+ setGlobals(globalFields): Promise<void> {
140
+ // console.log({globalFields})
141
+ return new Promise((resolve, reject) => {
142
+ this.apiRequest(`${this.endpoint}/globals`, {
143
+ method: "PATCH",
144
+ body: JSON.stringify({globalFields})
145
+ }).then(res => {
146
+ // console.log(res)
147
+ if (res.messages[0].code === "0") {
148
+ resolve()
149
+ } else {
150
+ reject(new FMError(res.messages[0].code, res.status, res))
151
+ }
152
+ })
153
+ .catch(e => {
154
+ reject(e)
155
+ })
156
+ })
157
+ }
158
+
159
+ script(name, parameter): Script {
160
+ return ({name, parameter} as Script)
161
+ }
162
+ }
163
+
164
+ class layout {
165
+ readonly conn: FileMakerConnection;
166
+ protected name: string;
167
+ metadata: any;
168
+ constructor(conn: FileMakerConnection, name: string) {
169
+ this.conn = conn
170
+ this.name = name
171
+ }
172
+
173
+ get endpoint() {
174
+ return `${this.conn.endpoint}/layouts/${this.name}`
175
+ }
176
+
177
+ getScript(script) {
178
+ return new script(this, script)
179
+ }
180
+
181
+ createRecord(): Promise<record | Error | void> {
182
+ return new Promise((resolve, reject) => {
183
+ // Get the layout's metadata
184
+ this.conn.apiRequest(`${this.endpoint}`).then(res => {
185
+ let fields = {}
186
+ for (let _field of res.response.fieldMetaData) {
187
+ fields[_field.name] = ""
188
+ }
189
+ resolve(new record(this, -1, 0, fields))
190
+ })
191
+ })
192
+ }
193
+
194
+ getRecord(recordId): record {
195
+ return new record(this, recordId)
196
+ }
197
+
198
+ newFind(): find {
199
+ return new find(this)
200
+ }
201
+
202
+ runScript(script): Promise<string | FMError | Error> {
203
+ return new Promise(async (resolve, reject) => {
204
+ let url = `${this.endpoint}/script/${encodeURI(script.name)}`
205
+ if (script.parameter) url += "?" + encodeURI(script.parameter)
206
+ this.conn.apiRequest(url, {
207
+ port: 443,
208
+ method: "GET"
209
+ })
210
+ .then(res => {
211
+ if (res.messages[0].code === "0") {
212
+ resolve(res.response)
213
+ } else {
214
+ // console.log(res)
215
+ reject(new FMError(res.messages[0].code, res.status, res))
216
+ }
217
+ })
218
+ .catch(e => {
219
+ reject(e)
220
+ })
221
+ })
222
+ }
223
+
224
+ public getLayoutMeta(): Promise<layout | FMError> {
225
+ return new Promise((resolve, reject) => {
226
+ this.conn.apiRequest(this.endpoint).then(res => {
227
+ this.metadata = res.response
228
+ resolve(this)
229
+ })
230
+ .catch(e => reject(e))
231
+ })
232
+ }
233
+ }
234
+
235
+ class record extends EventEmitter {
236
+ readonly layout: layout;
237
+ public recordId: number;
238
+ modId: number;
239
+ fields: field[];
240
+ readonly portals: portal[];
241
+ private portalData: any[];
242
+
243
+ constructor(layout, recordId, modId = recordId, fieldData = {}, portalData = null) {
244
+ super();
245
+ this.layout = layout
246
+ this.recordId = recordId
247
+ this.modId = modId
248
+ this.fields = this.processFieldData(fieldData)
249
+ this.portals = []
250
+ if (portalData) {
251
+ this.processPortalData(portalData)
252
+ }
253
+ this.on("saved", () => {
254
+ for (let field of this.fields) field.edited = false
255
+ })
256
+ }
257
+
258
+ get endpoint(): string {
259
+ return `${this.layout.endpoint}/records/${this.recordId}`
260
+ }
261
+
262
+ get edited(): boolean {
263
+ return !!this.fields.find(i => i.edited)
264
+ }
265
+
266
+ private processPortalData(portalData): void {
267
+ for (let item of Object.keys(portalData)) {
268
+ this.portals.push(new portal(this, item, portalData[item].map(item => {
269
+ let fieldData = item
270
+ delete fieldData.recordId
271
+ delete fieldData.modId
272
+ return new portalItem(this, item.recordId, item.modId, fieldData)
273
+ })))
274
+ }
275
+ }
276
+
277
+ processFieldData(fieldData): field[] {
278
+ return Object.keys(fieldData).map(item => {
279
+ return new field(this, item, fieldData[item])
280
+ })
281
+ }
282
+
283
+ get() {
284
+ return new Promise(async (resolve, reject) => {
285
+ if (!this.layout.metadata) await this.layout.getLayoutMeta()
286
+
287
+ this.layout.conn.apiRequest(this.endpoint, {
288
+ port: 443,
289
+ method: "GET"
290
+ })
291
+ .then(res => {
292
+ if (res.messages[0].code === "0") {
293
+ // console.log(res, res.response.data)
294
+ this.modId = res.response.data[0].modId
295
+ this.fields = this.processFieldData(res.response.data[0].fieldData)
296
+ this.portalData = []
297
+ if (res.response.data[0].portalData) this.processPortalData(res.response.data[0].portalData)
298
+ resolve(this)
299
+ } else {
300
+ reject(new FMError(res.messages[0].code, res.status, res))
301
+ }
302
+ })
303
+ .catch(e => {
304
+ reject(e)
305
+ })
306
+ })
307
+ }
308
+
309
+ commit(extraBody = {}): Promise<record | FMError> {
310
+ return new Promise(async (resolve, reject) => {
311
+ let data = this.toObject()
312
+ delete data.recordId
313
+ delete data.modId
314
+
315
+ if (this.recordId === -1) {
316
+ // This is a new record
317
+ this.layout.conn.apiRequest(`${this.layout.endpoint}/records`, {
318
+ port: 443,
319
+ method: "POST",
320
+ body: JSON.stringify({
321
+ fieldData: data.fieldData
322
+ })
323
+ })
324
+ .then(res => {
325
+ if (res.messages[0].code === "0") {
326
+ this.recordId = res.response.recordId
327
+ this.modId = res.response.modId
328
+ resolve(this)
329
+ } else {
330
+ reject(new FMError(res.messages[0].code, res.status, res))
331
+ }
332
+ })
333
+ .catch(e => {
334
+ reject(e)
335
+ })
336
+
337
+ return
338
+ }
339
+
340
+ for (let item of Object.keys(extraBody)) data[item] = extraBody[item]
341
+ this.layout.conn.apiRequest(this.endpoint, {
342
+ port: 443,
343
+ method: "PATCH",
344
+ body: JSON.stringify(data)
345
+ })
346
+ .then(res => {
347
+ if (res.messages[0].code === "0") {
348
+ this.modId = res.response.modId
349
+ this.emit("saved")
350
+ resolve(this)
351
+ } else {
352
+ reject(new FMError(res.messages[0].code, res.status, res))
353
+ }
354
+ })
355
+ .catch(e => {
356
+ reject(e)
357
+ })
358
+ })
359
+ }
360
+
361
+ getField(field) {
362
+ return this.fields.find(_field => _field.id === field)
363
+ }
364
+
365
+ getPortal(portal) {
366
+ return this.portals.find(p => p.name === portal)
367
+ }
368
+
369
+ toObject(filter = (a) => a.edited,
370
+ portalFilter = (a) => a.records.find(record => record.edited),
371
+ portalRowFilter = (a) => a.edited,
372
+ portalFieldFilter = (a) => a.edited
373
+ ): recordObject {
374
+ let fields_processed = {}
375
+ for (let field of this.fields.filter(field => filter(field))) {
376
+ fields_processed[field.id] = field.value
377
+ }
378
+ let obj = {
379
+ "recordId": this.recordId,
380
+ "modId": this.modId,
381
+ "fieldData": fields_processed
382
+ }
383
+
384
+ // Check if there's been any edited portal information
385
+ let portals = this.portals.filter(a => portalFilter(a))
386
+ if (portals) {
387
+ obj["portalData"] = {}
388
+ for (let portal of portals) {
389
+ obj["portalData"][portal.name] = portal.records.filter(a => portalRowFilter(a)).map(record => {
390
+ return record.toObject(portalFieldFilter)
391
+ })
392
+ }
393
+ }
394
+ return obj
395
+ }
396
+ }
397
+
398
+ class field {
399
+ record: record;
400
+ id: number;
401
+ value: string | number;
402
+ edited: boolean;
403
+ constructor(record, id, contents) {
404
+ this.record = record
405
+ this.id = id
406
+ this.value = contents
407
+ this.edited = false
408
+ }
409
+
410
+ set(content) {
411
+ if (this.dataType === "container") throw "Cannot set container value using set(). Use upload() instead."
412
+ this.value = content
413
+ this.edited = true
414
+ }
415
+
416
+ get dataType() {
417
+ if (!this.record.layout.metadata) return "unknown"
418
+ if (this.record instanceof portalItem) {
419
+ return this.record.layout.metadata.portalMetaData.find(i => i.name === this.id).result || "unknown"
420
+ } else {
421
+ return this.record.layout.metadata.fieldMetaData.find(i => i.name === this.id).result || "unknown"
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)"
427
+ return new Promise(async (resolve, reject) => {
428
+ let form = new FormData()
429
+ form.append("upload", new File([buffer], filename, {type: mime}))
430
+
431
+ let _fetch = await fetch(`${this.record.endpoint}/containers/${this.id}/1`, {
432
+ method: "POST",
433
+ headers: {"Authorization": "Bearer " + this.record.layout.conn.token},
434
+ body: form
435
+ }).then(res => res.json())
436
+ .then(data => {
437
+ let _res = data as any
438
+ if (_res.messages[0].code === "0") {
439
+ resolve()
440
+ } else {
441
+ reject(new FMError(_res.messages[0].code, _res.status, data))
442
+ }
443
+ })
444
+ .catch(e => {reject(e)})
445
+ })
446
+ }
447
+ download() {
448
+ return new Promise((resolve, reject) => {
449
+ https.get(this.value.toString(), (res) => {
450
+ resolve(res)
451
+ })
452
+ })
453
+ }
454
+ }
455
+
456
+ class portal {
457
+ readonly record: record;
458
+ readonly name: string;
459
+ public records: portalItem[];
460
+
461
+ constructor(record: record, name: string, records: portalItem[] = []) {
462
+ this.record = record
463
+ this.name = name
464
+ this.records = records
465
+ for (let item of this.records) item.attachPortal(this)
466
+ }
467
+ }
468
+
469
+ class portalItem extends record {
470
+ protected portal: portal;
471
+
472
+ constructor(record, recordId, modId = recordId, fieldData = {}) {
473
+ super(record.layout, recordId, modId, fieldData);
474
+ record.on("saved", () => {
475
+ this.emit("saved")
476
+ })
477
+ }
478
+
479
+ attachPortal(portal) {
480
+ this.portal = portal
481
+ }
482
+
483
+ commit(extraBody = {}) {
484
+ return this.portal.record.commit(extraBody)
485
+ }
486
+
487
+ toObject(fieldFilter): any {
488
+ super.toObject()
489
+ let res = {
490
+ recordId: this.recordId, modId: this.modId
491
+ } as recordObject
492
+ for (let field of this.fields.filter(a => fieldFilter(a))) res[field.id] = field.value
493
+ // console.log(res)
494
+ return res
495
+ }
496
+ }
497
+
498
+ class find {
499
+ protected layout: layout
500
+ protected queries: object[]
501
+ protected scripts: object
502
+ protected sort: object[]
503
+
504
+ constructor(layout) {
505
+ this.layout = layout
506
+ this.queries = []
507
+ this.scripts = {
508
+ "script": null, // Runs after everything
509
+ "script.prerequeset": null, // Runs before the request
510
+ "script.presort": null // Runs before the sort
511
+ }
512
+ this.sort = []
513
+ }
514
+
515
+ private toObject() {
516
+ let out = {query: this.queries, sort: undefined}
517
+ if (this.sort.length !== 0) out.sort = this.sort
518
+
519
+ for (let item of Object.keys(this.scripts)) {
520
+ if (this.scripts[item]) {
521
+ out[item] = this.scripts[item].name
522
+ if (this.scripts[item].parameter) out[item + ".param"] = this.scripts[item].parameter
523
+ }
524
+ }
525
+
526
+ return out
527
+ }
528
+
529
+ addRequests(...requests) {
530
+ for (let item of requests) this.queries.push(item)
531
+ return this
532
+ }
533
+
534
+ addSort(fieldName, sortOrder) {
535
+ this.sort.push({fieldName, sortOrder})
536
+ return this
537
+ }
538
+
539
+ find() {
540
+ return new Promise((resolve, reject) => {
541
+ // console.log(this.#toObject())
542
+ this.layout.conn.apiRequest(`${this.layout.endpoint}/_find`, {
543
+ port: 443,
544
+ method: "POST",
545
+ body: JSON.stringify(this.toObject())
546
+ }).then(async res => {
547
+ // // console.log(res)
548
+ if (res.messages[0].code === "0") {
549
+ // console.log("RESOLVING")
550
+ if (!this.layout.metadata) await this.layout.getLayoutMeta()
551
+ let data = res.response.data.map(item => {
552
+ return new record(this.layout, item.recordId, item.modId, item.fieldData, item.portalData)
553
+ })
554
+ resolve(data)
555
+ } else {
556
+ reject(new FMError(res.messages[0].code, res.status, res))
557
+ }
558
+ })
559
+ .catch(e => {
560
+ reject(e)
561
+ })
562
+ })
563
+ }
564
+ }
565
+
566
+ export class FMError extends Error {
567
+ readonly httpStatus: number
568
+ readonly res: any
569
+ readonly code: number
570
+ readonly message: string
571
+
572
+ constructor(code, httpStatus, res) {
573
+ super(errs.find(err => err.e === this.code) || "Unknown error");
574
+ this.httpStatus = httpStatus
575
+ this.res = res
576
+ this.code = typeof code === "string" ? parseInt(code) : code
577
+ Error.captureStackTrace(this, FMError)
578
+ }
579
+ }
580
+
581
+ //
582
+ // module.exports = {
583
+ // default: {FileMakerConnection}
585
584
  // }