@jd-data-limited/easy-fm 4.0.16 → 4.1.0

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.
Files changed (63) hide show
  1. package/package.json +12 -6
  2. package/src/FMError.d.ts +2 -2
  3. package/src/FMError.js +507 -508
  4. package/src/FMError.ts +510 -511
  5. package/src/connection/FMHost.d.ts +23 -5
  6. package/src/connection/FMHost.js +42 -26
  7. package/src/connection/FMHost.ts +63 -49
  8. package/src/connection/HostBase.d.ts +3 -3
  9. package/src/connection/HostBase.ts +5 -5
  10. package/src/connection/database.d.ts +50 -17
  11. package/src/connection/database.js +94 -66
  12. package/src/connection/database.ts +125 -105
  13. package/src/connection/databaseBase.d.ts +11 -6
  14. package/src/connection/databaseBase.ts +9 -8
  15. package/src/connection/generateAuthorizationHeaders.d.ts +1 -1
  16. package/src/connection/generateAuthorizationHeaders.js +10 -10
  17. package/src/connection/generateAuthorizationHeaders.ts +21 -21
  18. package/src/databaseStructure.d.ts +2 -4
  19. package/src/databaseStructure.ts +3 -5
  20. package/src/index.d.ts +17 -17
  21. package/src/index.js +13 -13
  22. package/src/index.ts +24 -24
  23. package/src/layouts/layout.d.ts +17 -6
  24. package/src/layouts/layout.js +19 -8
  25. package/src/layouts/layout.ts +33 -23
  26. package/src/layouts/layoutBase.d.ts +5 -5
  27. package/src/layouts/layoutBase.ts +6 -6
  28. package/src/layouts/layoutInterface.d.ts +3 -5
  29. package/src/layouts/layoutInterface.ts +4 -6
  30. package/src/layouts/layoutRecordManager.d.ts +29 -6
  31. package/src/layouts/layoutRecordManager.js +32 -9
  32. package/src/layouts/layoutRecordManager.ts +42 -19
  33. package/src/layouts/recordFieldsMap.d.ts +2 -4
  34. package/src/layouts/recordFieldsMap.ts +2 -4
  35. package/src/models/apiResults.d.ts +7 -13
  36. package/src/models/apiResults.ts +58 -58
  37. package/src/records/field.d.ts +42 -12
  38. package/src/records/field.js +69 -59
  39. package/src/records/field.ts +102 -81
  40. package/src/records/getOperations/recordGetOperation.d.ts +44 -24
  41. package/src/records/getOperations/recordGetOperation.js +87 -61
  42. package/src/records/getOperations/recordGetOperation.ts +127 -108
  43. package/src/records/layoutRecord.d.ts +28 -14
  44. package/src/records/layoutRecord.js +69 -60
  45. package/src/records/layoutRecord.ts +121 -119
  46. package/src/records/layoutRecordBase.d.ts +2 -2
  47. package/src/records/layoutRecordBase.ts +3 -3
  48. package/src/records/portal.d.ts +19 -5
  49. package/src/records/portal.js +19 -5
  50. package/src/records/portal.ts +28 -16
  51. package/src/records/portalBase.d.ts +5 -5
  52. package/src/records/portalBase.ts +6 -6
  53. package/src/records/portalRecord.d.ts +16 -8
  54. package/src/records/portalRecord.js +16 -6
  55. package/src/records/portalRecord.ts +29 -19
  56. package/src/records/recordBase.d.ts +17 -7
  57. package/src/records/recordBase.js +23 -16
  58. package/src/records/recordBase.ts +41 -37
  59. package/src/types.d.ts +21 -24
  60. package/src/types.ts +55 -59
  61. package/src/utils/query.d.ts +7 -7
  62. package/src/utils/query.js +10 -10
  63. package/src/utils/query.ts +19 -20
@@ -1,13 +1,17 @@
1
1
  /*
2
2
  * Copyright (c) 2023-2024. See LICENSE file for more information
3
3
  */
4
- import { EventEmitter } from "events";
5
- import { generateAuthorizationHeaders } from "./generateAuthorizationHeaders.js";
6
- import { FMError } from "../FMError.js";
7
- import { Layout } from "../layouts/layout.js";
8
- import HTTP_REDIRECT from "follow-redirects";
4
+ import { EventEmitter } from 'events';
5
+ import { generateAuthorizationHeaders } from './generateAuthorizationHeaders.js';
6
+ import { FMError } from '../FMError.js';
7
+ import { Layout } from '../layouts/layout.js';
8
+ import HTTP_REDIRECT from 'follow-redirects';
9
+ /**
10
+ * Represents a database connection.
11
+ * @template T - The structure of the database.
12
+ */
9
13
  export class Database extends EventEmitter {
10
- _token;
14
+ _token = '';
11
15
  host;
12
16
  connection_details;
13
17
  cookies = {};
@@ -18,11 +22,12 @@ export class Database extends EventEmitter {
18
22
  this.host = host;
19
23
  this.name = conn.database;
20
24
  this.connection_details = conn;
21
- this.debug = !!conn.debug;
25
+ this.debug = conn.debug ?? false;
22
26
  }
27
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
23
28
  generateExternalSourceLogin(data) {
24
- if (data.credentials.method === "filemaker") {
25
- let _data = data.credentials;
29
+ if (data.credentials.method === 'filemaker') {
30
+ const _data = data.credentials;
26
31
  return {
27
32
  database: data.database,
28
33
  username: _data.username,
@@ -30,50 +35,66 @@ export class Database extends EventEmitter {
30
35
  };
31
36
  }
32
37
  else {
33
- throw "Not yet supported login method";
38
+ throw new Error('Not yet supported login method');
34
39
  }
35
40
  }
41
+ /**
42
+ * Logs out the user by deleting the current session token.
43
+ * Throws an error if the user is not logged in.
44
+ *
45
+ * @returns {Promise<void>} A promise that resolves with no value once the logout is successful.
46
+ * @throws {Error} Throws an error if the user is not logged in.
47
+ */
36
48
  async logout() {
37
- if (!this.token)
38
- throw new Error("Not logged in");
39
- let _fetch = await fetch(`${this.endpoint}/sessions/${this.token}`, {
40
- method: "DELETE",
49
+ if (this.token === '')
50
+ throw new Error('Not logged in');
51
+ const _fetch = await fetch(`${this.endpoint}/sessions/${this.token}`, {
52
+ method: 'DELETE',
41
53
  headers: {
42
- "content-type": "application/json"
54
+ 'content-type': 'application/json'
43
55
  }
44
56
  });
45
- let data = await _fetch.json();
57
+ await _fetch.json();
46
58
  // console.log(data)
47
- this._token = null;
59
+ this._token = '';
48
60
  }
61
+ /**
62
+ * Logs in to the database. Not required, as this is often done automatically
63
+ *
64
+ * @param {boolean} [forceLogin=false] - Whether to force login even if already logged in.
65
+ * @throws {Error} - Throws an error if already logged in and forceLogin is false.
66
+ * @throws {FMError} - Throws an FMError if login fails.
67
+ * @return {Promise<string>} - Returns a promise that resolves to the access token upon successful login.
68
+ */
49
69
  async login(forceLogin = false) {
70
+ // console.trace()
50
71
  // Reset cookies
51
72
  this.cookies = {};
52
73
  await this.host.getMetadata();
53
- if (this.token && !forceLogin)
54
- throw new Error("Already logged in. Run logout() first");
55
- if (this.connection_details.credentials.method === "token") {
56
- this._token = this.connection_details.credentials.token;
74
+ if (this.token !== '' && !forceLogin)
75
+ throw new Error('Already logged in. Run logout() first');
76
+ if (this.connection_details.credentials.method === 'token') {
77
+ this._token = (this.connection_details.credentials).token;
57
78
  return this.token;
58
79
  }
59
80
  else {
60
81
  console.time();
61
- let url = new URL(`${this.endpoint}/sessions`);
82
+ const url = new URL(`${this.endpoint}/sessions`);
62
83
  url.hostname = this.host.hostname;
63
- let res = await fetch(url, {
64
- method: "POST",
84
+ const res = await fetch(url, {
85
+ method: 'POST',
65
86
  headers: generateAuthorizationHeaders(this.connection_details.credentials),
66
87
  body: JSON.stringify({
67
88
  fmDataSource: this.connection_details.externalSources.map(i => {
68
- let _i = i;
89
+ const _i = i;
69
90
  return this.generateExternalSourceLogin(_i);
70
91
  })
71
92
  })
72
93
  });
73
94
  console.time();
74
- let _res = (await res.json());
95
+ const _res = (await res.json());
75
96
  if (res.status === 200) {
76
- this._token = res.headers.get('x-fm-data-access-token');
97
+ this._token = res.headers.get('x-fm-data-access-token') ?? '';
77
98
  return this._token;
78
99
  }
79
100
  else {
@@ -84,54 +105,65 @@ export class Database extends EventEmitter {
84
105
  get token() {
85
106
  return this._token;
86
107
  }
108
+ /**
109
+ * Returns the endpoint URL for the database connection.
110
+ *
111
+ * @returns {string} The endpoint URL.
112
+ */
87
113
  get endpoint() {
88
114
  return `${this.host.hostname}/fmi/data/v2/databases/${this.name}`;
89
115
  }
90
- async apiRequestRaw(url, options = {}, autoRelogin = true) {
116
+ async _apiRequestRaw(url, options = {}, autoRelogin = true) {
91
117
  if (this.debug)
92
- console.log(`EASYFM DEBUG: ${options.method?.toString().toUpperCase() || "GET"} ${url instanceof Request ? url.url : url}`);
93
- if (!this.token)
118
+ console.log(`EASYFM DEBUG: ${JSON.stringify(options)} ${url instanceof Request ? url.url : url}`);
119
+ if (this.token === '')
94
120
  await this.login(true);
95
121
  if (!options.headers)
96
122
  options.headers = {};
97
- options.headers["authorization"] = "Bearer " + this._token;
98
- options.rejectUnauthorized = this.host.verify;
99
- let _fetch = await fetch(url, options);
123
+ options.headers.authorization = 'Bearer ' + this._token;
124
+ // options.rejectUnauthorized = this.host.verify
125
+ const _fetch = await fetch(url, options);
100
126
  if (_fetch.headers.get('set-cookie')) {
101
- for (let cookie of _fetch.headers.get('set-cookie') || []) {
102
- let cookie_split = cookie.split("=");
103
- this.cookies[cookie_split[0]] = cookie_split[1];
127
+ for (const cookie of _fetch.headers.get('set-cookie') ?? []) {
128
+ const cookieSplit = cookie.split('=');
129
+ this.cookies[cookieSplit[0]] = cookieSplit[1];
104
130
  }
105
131
  }
106
132
  if (_fetch.status === 401 && autoRelogin) {
107
133
  await this.login(true);
108
- return await this.apiRequestRaw(url, options, false);
134
+ return await this._apiRequestRaw(url, options, false);
109
135
  }
110
136
  return _fetch;
111
137
  }
112
- async apiRequestJSON(url, options = {}) {
138
+ async _apiRequestJSON(url, options = {}) {
113
139
  if (!options.headers)
114
140
  options.headers = {};
115
- options.headers["content-type"] = options.headers["content-type"] ? options.headers["content-type"] : "application/json";
116
- let _fetch = await this.apiRequestRaw(url, options);
117
- let data = await _fetch.json();
141
+ options.headers['content-type'] = options.headers['content-type'] ? options.headers['content-type'] : 'application/json';
142
+ const _fetch = await this._apiRequestRaw(url, options);
143
+ const data = await _fetch.json();
118
144
  // Remove response if it is empty. This makes checking for an empty response easier
119
145
  if (data.response && Object.keys(data.response).length === 0)
120
146
  delete data.response;
121
147
  // console.log(data.messages[0])
122
- if (data.messages[0].code !== "0") {
148
+ if (data.messages[0].code !== '0') {
123
149
  throw new FMError(data.messages[0].code, _fetch.status, data);
124
150
  }
125
151
  data.httpStatus = _fetch.status;
126
152
  return data;
127
153
  }
154
+ /**
155
+ * Retrieves a list of layouts in the current FileMaker database.
156
+ *
157
+ * @returns {Promise<Layout[]>} A promise that resolves to an array of Layout objects.
158
+ * @throws {FMError} If there was an error retrieving the layouts.
159
+ */
128
160
  async listLayouts() {
129
- let req = await this.apiRequestJSON(`${this.endpoint}/layouts?page=2`);
161
+ const req = await this._apiRequestJSON(`${this.endpoint}/layouts?page=2`);
130
162
  if (!req.response)
131
163
  throw new FMError(req.messages[0].code, req.httpStatus, req.messages[0].message);
132
164
  const cycleLayoutNames = (layouts) => {
133
165
  let names = [];
134
- for (let layout of layouts) {
166
+ for (const layout of layouts) {
135
167
  if (layout.folderLayoutNames)
136
168
  names = names.concat(cycleLayoutNames(layout.folderLayoutNames));
137
169
  else
@@ -139,47 +171,43 @@ export class Database extends EventEmitter {
139
171
  }
140
172
  return names;
141
173
  };
142
- let layoutNames;
143
174
  return cycleLayoutNames(req.response.layouts).map(layout => new Layout(this, layout));
144
175
  }
145
- getLayout(name) {
176
+ layout(name) {
146
177
  return new Layout(this, name);
147
178
  }
148
- script(name, parameter = "") {
149
- return { name, parameter };
150
- }
151
- _tokenExpired() {
152
- this.emit("token_expired");
179
+ script(name, parameter = '') {
180
+ return ({ name, parameter });
153
181
  }
154
- streamURL(url) {
155
- return new Promise((resolve, reject) => {
156
- if (!url || typeof url !== "string") {
157
- throw new Error("URL is empty, or has invalid value");
182
+ async streamURL(url) {
183
+ return await new Promise((resolve, reject) => {
184
+ if (!url || typeof url !== 'string') {
185
+ throw new Error('URL is empty, or has invalid value');
158
186
  }
159
- let headers = {};
187
+ const headers = {};
160
188
  if (Object.keys(this.cookies).length !== 0) {
161
- headers["Cookie"] = Object.keys(this.cookies)
189
+ headers.Cookie = Object.keys(this.cookies)
162
190
  .map(key => {
163
- return key + "=" + this.cookies[key];
191
+ return key + '=' + this.cookies[key];
164
192
  })
165
- .join("; ");
193
+ .join('; ');
166
194
  }
167
- const check_for_cookies = (res) => {
195
+ const checkForCookies = (res) => {
168
196
  // Check for the 'set-cookie' header. If it exists, remember it and strip it for better security.
169
197
  if (res.headers['set-cookie']) {
170
- for (let cookie of res.headers['set-cookie']) {
171
- let cookie_split = cookie.split("=");
172
- this.cookies[cookie_split[0]] = cookie_split[1];
198
+ for (const cookie of res.headers['set-cookie']) {
199
+ const cookieSplit = cookie.split('=');
200
+ this.cookies[cookieSplit[0]] = cookieSplit[1];
173
201
  }
174
202
  res.headers['set-cookie'] = undefined;
175
203
  }
176
204
  };
177
205
  // Automatically switch between the http and https modules, based on which is needed
178
- (url.startsWith("https") ? HTTP_REDIRECT.https : HTTP_REDIRECT.http).get(url, {
206
+ (url.startsWith('https') ? HTTP_REDIRECT.https : HTTP_REDIRECT.http).get(url, {
179
207
  headers,
180
- beforeRedirect: (options, res) => check_for_cookies(res)
208
+ beforeRedirect: (options, res) => { checkForCookies(res); }
181
209
  }, (res) => {
182
- check_for_cookies(res);
210
+ checkForCookies(res);
183
211
  resolve(res);
184
212
  });
185
213
  });
@@ -2,149 +2,169 @@
2
2
  * Copyright (c) 2023-2024. See LICENSE file for more information
3
3
  */
4
4
 
5
- import {EventEmitter} from "events";
6
- import {generateAuthorizationHeaders} from "./generateAuthorizationHeaders.js";
7
- import {FMError} from "../FMError.js";
8
- import {LayoutInterface} from "../layouts/layoutInterface.js";
9
- import {Layout} from "../layouts/layout.js";
10
- import HTTP_REDIRECT from "follow-redirects"
11
- import {
12
- databaseOptionsBase,
13
- databaseOptionsWithExternalSources,
14
- loginOptionsFileMaker,
15
- loginOptionsToken,
16
- Script
17
- } from "../types.js";
18
- import {HostBase} from "./HostBase.js"
19
- import {DatabaseBase} from "./databaseBase.js";
20
- import {ApiLayout, ApiResults} from "../models/apiResults.js";
21
- import {DatabaseStructure} from "../databaseStructure.js";
22
- import {IncomingMessage} from "http";
23
-
24
- type GetLayoutReturnType<T extends DatabaseStructure, R extends LayoutInterface | string> = R extends string ? T["layouts"][R] : R
25
-
5
+ import {EventEmitter} from 'events'
6
+ import {generateAuthorizationHeaders} from './generateAuthorizationHeaders.js'
7
+ import {FMError} from '../FMError.js'
8
+ import {type LayoutInterface} from '../layouts/layoutInterface.js'
9
+ import {Layout} from '../layouts/layout.js'
10
+ import HTTP_REDIRECT from 'follow-redirects'
11
+ import {type databaseOptionsBase, type databaseOptionsWithExternalSources, type Script} from '../types.js'
12
+ import {type HostBase} from './HostBase.js'
13
+ import {type DatabaseBase} from './databaseBase.js'
14
+ import {type ApiLayout, type ApiResults} from '../models/apiResults.js'
15
+ import {type DatabaseStructure} from '../databaseStructure.js'
16
+ import {type IncomingMessage} from 'http'
17
+
18
+ /**
19
+ * Represents a database connection.
20
+ * @template T - The structure of the database.
21
+ */
26
22
  export class Database<T extends DatabaseStructure> extends EventEmitter implements DatabaseBase {
27
- private _token: any;
28
- readonly host: HostBase;
29
- private connection_details: databaseOptionsWithExternalSources
30
- private cookies: { [key: string]: string } = {}
31
- readonly name: string;
23
+ private _token: string = ''
24
+ readonly host: HostBase
25
+ private readonly connection_details: databaseOptionsWithExternalSources
26
+ private cookies: Record<string, string> = {}
27
+ readonly name: string
32
28
  readonly debug: boolean
33
29
 
34
- constructor(host: HostBase, conn: databaseOptionsWithExternalSources) {
30
+ constructor (host: HostBase, conn: databaseOptionsWithExternalSources) {
35
31
  super()
36
32
  this.host = host
37
33
  this.name = conn.database
38
34
  this.connection_details = conn
39
- this.debug = !!conn.debug
35
+ this.debug = conn.debug ?? false
40
36
  }
41
37
 
42
- private generateExternalSourceLogin(data: databaseOptionsBase) {
43
- if (data.credentials.method === "filemaker") {
44
- let _data = <loginOptionsFileMaker>data.credentials
38
+ // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
39
+ private generateExternalSourceLogin (data: databaseOptionsBase) {
40
+ if (data.credentials.method === 'filemaker') {
41
+ const _data = data.credentials
45
42
  return {
46
43
  database: data.database,
47
44
  username: _data.username,
48
45
  password: _data.password
49
46
  }
50
- }
51
- else {
52
- throw "Not yet supported login method"
47
+ } else {
48
+ throw new Error('Not yet supported login method')
53
49
  }
54
50
  }
55
51
 
56
- async logout(): Promise<void> {
57
- if (!this.token) throw new Error("Not logged in")
58
-
59
- let _fetch = await fetch(`${this.endpoint}/sessions/${this.token}`, {
60
- method: "DELETE",
52
+ /**
53
+ * Logs out the user by deleting the current session token.
54
+ * Throws an error if the user is not logged in.
55
+ *
56
+ * @returns {Promise<void>} A promise that resolves with no value once the logout is successful.
57
+ * @throws {Error} Throws an error if the user is not logged in.
58
+ */
59
+ async logout (): Promise<void> {
60
+ if (this.token === '') throw new Error('Not logged in')
61
+
62
+ const _fetch = await fetch(`${this.endpoint}/sessions/${this.token}`, {
63
+ method: 'DELETE',
61
64
  headers: {
62
- "content-type": "application/json"
65
+ 'content-type': 'application/json'
63
66
  }
64
67
  })
65
- let data = await _fetch.json()
68
+ await _fetch.json()
66
69
  // console.log(data)
67
- this._token = null
70
+ this._token = ''
68
71
  }
69
72
 
70
- async login(forceLogin = false) {
73
+ /**
74
+ * Logs in to the database. Not required, as this is often done automatically
75
+ *
76
+ * @param {boolean} [forceLogin=false] - Whether to force login even if already logged in.
77
+ * @throws {Error} - Throws an error if already logged in and forceLogin is false.
78
+ * @throws {FMError} - Throws an FMError if login fails.
79
+ * @return {Promise<string>} - Returns a promise that resolves to the access token upon successful login.
80
+ */
81
+ async login (forceLogin = false) {
82
+ // console.trace()
71
83
  // Reset cookies
72
84
  this.cookies = {}
73
85
 
74
86
  await this.host.getMetadata()
75
- if (this.token && !forceLogin) throw new Error("Already logged in. Run logout() first")
87
+ if (this.token !== '' && !forceLogin) throw new Error('Already logged in. Run logout() first')
76
88
 
77
- if (this.connection_details.credentials.method === "token") {
78
- this._token = (<loginOptionsToken>this.connection_details.credentials).token
89
+ if (this.connection_details.credentials.method === 'token') {
90
+ this._token = (this.connection_details.credentials).token
79
91
  return this.token
80
- }
81
- else {
92
+ } else {
82
93
  console.time()
83
- let url = new URL(`${this.endpoint}/sessions`)
94
+ const url = new URL(`${this.endpoint}/sessions`)
84
95
  url.hostname = this.host.hostname
85
- let res = await fetch(url, {
86
- method: "POST",
96
+ const res = await fetch(url, {
97
+ method: 'POST',
87
98
  headers: generateAuthorizationHeaders(this.connection_details.credentials) as unknown as HeadersInit,
88
99
  body: JSON.stringify({
89
100
  fmDataSource: this.connection_details.externalSources.map(i => {
90
- let _i = <databaseOptionsBase>i
101
+ const _i = i
91
102
  return this.generateExternalSourceLogin(_i)
92
103
  })
93
104
  })
94
105
  })
95
106
  console.time()
96
- let _res = <any>(await res.json())
107
+ const _res = (await res.json())
97
108
  if (res.status === 200) {
98
- this._token = res.headers.get('x-fm-data-access-token')
109
+ this._token = res.headers.get('x-fm-data-access-token') ?? ''
99
110
  return this._token
100
- }
101
- else {
102
- throw new FMError(_res.messages[0].code, _res.status, res)
111
+ } else {
112
+ throw new FMError(
113
+ _res.messages[0].code as string | number,
114
+ _res.status as number,
115
+ res
116
+ )
103
117
  }
104
118
  }
105
119
  }
106
120
 
107
- get token() {
121
+ get token () {
108
122
  return this._token
109
123
  }
110
124
 
111
- get endpoint(): string {
125
+ /**
126
+ * Returns the endpoint URL for the database connection.
127
+ *
128
+ * @returns {string} The endpoint URL.
129
+ */
130
+ get endpoint (): string {
112
131
  return `${this.host.hostname}/fmi/data/v2/databases/${this.name}`
113
132
  }
114
133
 
115
- async apiRequestRaw(url: string | Request, options: any = {}, autoRelogin = true): Promise<Response> {
116
- if (this.debug) console.log(`EASYFM DEBUG: ${options.method?.toString().toUpperCase() || "GET"} ${url instanceof Request ? url.url : url}`)
117
- if (!this.token) await this.login(true)
134
+ async _apiRequestRaw (url: string | Request, options: RequestInit & { headers?: Record<string, string> } = {}, autoRelogin = true): Promise<Response> {
135
+ if (this.debug) console.log(`EASYFM DEBUG: ${JSON.stringify(options)} ${url instanceof Request ? url.url : url}`)
136
+ if (this.token === '') await this.login(true)
118
137
 
119
138
  if (!options.headers) options.headers = {}
120
- options.headers["authorization"] = "Bearer " + this._token
121
- options.rejectUnauthorized = this.host.verify
139
+ options.headers.authorization = 'Bearer ' + this._token
140
+ // options.rejectUnauthorized = this.host.verify
122
141
 
123
- let _fetch = await fetch(url, options)
142
+ const _fetch = await fetch(url, options)
124
143
  if (_fetch.headers.get('set-cookie')) {
125
- for (let cookie of _fetch.headers.get('set-cookie') || []) {
126
- let cookie_split = cookie.split("=")
127
- this.cookies[cookie_split[0]] = cookie_split[1]
144
+ for (const cookie of _fetch.headers.get('set-cookie') ?? []) {
145
+ const cookieSplit = cookie.split('=')
146
+ this.cookies[cookieSplit[0]] = cookieSplit[1]
128
147
  }
129
148
  }
130
149
 
131
150
  if (_fetch.status === 401 && autoRelogin) {
132
151
  await this.login(true)
133
- return await this.apiRequestRaw(url, options, false)
152
+ return await this._apiRequestRaw(url, options, false)
134
153
  }
135
154
  return _fetch
136
155
  }
137
- async apiRequestJSON<T = any>(url: string | Request, options: any = {}): Promise<ApiResults<T>> {
156
+
157
+ async _apiRequestJSON<T = any>(url: string | Request, options: RequestInit & { headers?: Record<string, string> } = {}): Promise<ApiResults<T>> {
138
158
  if (!options.headers) options.headers = {}
139
- options.headers["content-type"] = options.headers["content-type"] ? options.headers["content-type"] : "application/json"
140
- let _fetch = await this.apiRequestRaw(url, options)
141
- let data = await _fetch.json() as ApiResults<T>
159
+ options.headers['content-type'] = options.headers['content-type'] ? options.headers['content-type'] : 'application/json'
160
+ const _fetch = await this._apiRequestRaw(url, options)
161
+ const data = await _fetch.json() as ApiResults<T>
142
162
 
143
163
  // Remove response if it is empty. This makes checking for an empty response easier
144
164
  if (data.response && Object.keys(data.response).length === 0) delete data.response
145
165
 
146
166
  // console.log(data.messages[0])
147
- if (data.messages[0].code !== "0") {
167
+ if (data.messages[0].code !== '0') {
148
168
  throw new FMError(data.messages[0].code, _fetch.status, data)
149
169
  }
150
170
 
@@ -152,72 +172,72 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
152
172
  return data
153
173
  }
154
174
 
155
- async listLayouts() {
156
- let req = await this.apiRequestJSON<{layouts: ApiLayout[]}>(`${this.endpoint}/layouts?page=2`)
175
+ /**
176
+ * Retrieves a list of layouts in the current FileMaker database.
177
+ *
178
+ * @returns {Promise<Layout[]>} A promise that resolves to an array of Layout objects.
179
+ * @throws {FMError} If there was an error retrieving the layouts.
180
+ */
181
+ async listLayouts () {
182
+ const req = await this._apiRequestJSON<{ layouts: ApiLayout[] }>(`${this.endpoint}/layouts?page=2`)
157
183
  if (!req.response) throw new FMError(req.messages[0].code, req.httpStatus, req.messages[0].message)
158
184
 
159
185
  const cycleLayoutNames = (layouts: ApiLayout[]) => {
160
- let names: string[] = []
161
- for (let layout of layouts) {
186
+ let names: string[] = []
187
+ for (const layout of layouts) {
162
188
  if (layout.folderLayoutNames) names = names.concat(cycleLayoutNames(layout.folderLayoutNames))
163
189
  else names.push(layout.name)
164
190
  }
165
191
  return names
166
192
  }
167
- let layoutNames
168
-
169
193
  return cycleLayoutNames(req.response.layouts).map(layout => new Layout(this, layout))
170
194
  }
171
195
 
172
- getLayout<R extends keyof T["layouts"]>(name: R): Layout<T["layouts"][R]>
173
- getLayout<R extends LayoutInterface>(name: string): Layout<R>
174
- getLayout(name: string): Layout<any> {
196
+ layout<R extends keyof T['layouts']>(name: R): Layout<T['layouts'][R]>
197
+ layout<R extends LayoutInterface>(name: string): Layout<R>
198
+ layout (name: string): Layout<any> {
175
199
  return new Layout<LayoutInterface>(this, name)
176
200
  }
177
201
 
178
- script(name: string, parameter = ""): Script {
179
- return ({name, parameter} as Script)
180
- }
181
-
182
- _tokenExpired() {
183
- this.emit("token_expired")
202
+ script (name: string, parameter = ''): Script {
203
+ return ({ name, parameter } satisfies Script)
184
204
  }
185
205
 
186
- streamURL(url: string): Promise<IncomingMessage> {
187
- return new Promise((resolve, reject) => {
188
- if (!url || typeof url !== "string") {
189
- throw new Error("URL is empty, or has invalid value")
206
+ async streamURL (url: string): Promise<IncomingMessage> {
207
+ return await new Promise((resolve, reject) => {
208
+ if (!url || typeof url !== 'string') {
209
+ throw new Error('URL is empty, or has invalid value')
190
210
  }
191
211
 
192
- let headers: {[key: string]: string} = {}
212
+ const headers: Record<string, string> = {}
193
213
  if (Object.keys(this.cookies).length !== 0) {
194
- headers["Cookie"] = Object.keys(this.cookies)
214
+ headers.Cookie = Object.keys(this.cookies)
195
215
  .map(key => {
196
- return key + "=" + this.cookies[key]
216
+ return key + '=' + this.cookies[key]
197
217
  })
198
- .join("; ")
218
+ .join('; ')
199
219
  }
200
220
 
201
- const check_for_cookies = (res: HTTP_REDIRECT.ResponseDetails) => {
221
+ const checkForCookies = (res: HTTP_REDIRECT.ResponseDetails) => {
202
222
  // Check for the 'set-cookie' header. If it exists, remember it and strip it for better security.
203
223
  if (res.headers['set-cookie']) {
204
- for (let cookie of res.headers['set-cookie']) {
205
- let cookie_split = cookie.split("=")
206
- this.cookies[cookie_split[0]] = cookie_split[1]
224
+ for (const cookie of res.headers['set-cookie']) {
225
+ const cookieSplit = cookie.split('=')
226
+ this.cookies[cookieSplit[0]] = cookieSplit[1]
207
227
  }
208
228
  res.headers['set-cookie'] = undefined
209
229
  }
210
230
  }
211
231
 
212
232
  // Automatically switch between the http and https modules, based on which is needed
213
- (url.startsWith("https") ? HTTP_REDIRECT.https : HTTP_REDIRECT.http).get(url, {
233
+ (url.startsWith('https') ? HTTP_REDIRECT.https : HTTP_REDIRECT.http).get(url, {
214
234
  headers,
215
- beforeRedirect: (options: any, res: any) => check_for_cookies(res)
235
+ beforeRedirect: (options: any, res: HTTP_REDIRECT.ResponseDetails) => { checkForCookies(res) }
216
236
  }, (res) => {
217
- check_for_cookies(res)
237
+ checkForCookies(res)
218
238
 
219
239
  resolve(res)
220
240
  })
221
241
  })
222
242
  }
223
- }
243
+ }
@@ -1,12 +1,17 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
- import { HostBase } from "./HostBase.js";
3
- import { ApiResults } from "../models/apiResults.js";
4
- import { IncomingMessage } from "http";
2
+ import { type HostBase } from './HostBase.js';
3
+ import { type ApiResults } from '../models/apiResults.js';
4
+ import { type IncomingMessage } from 'http';
5
5
  export interface DatabaseBase {
6
6
  host: HostBase;
7
7
  readonly name: string;
8
8
  endpoint: string;
9
- apiRequestJSON<T = unknown>(url: string | Request, options?: RequestInit | undefined, autoRelogin?: boolean): Promise<ApiResults<T>>;
10
- apiRequestRaw(url: string | Request, options?: RequestInit | undefined): Promise<Response>;
11
- streamURL(url: string): Promise<IncomingMessage>;
9
+ token: string;
10
+ _apiRequestJSON: <T = unknown>(url: string | Request, options?: RequestInit & {
11
+ headers?: Record<string, string>;
12
+ } | undefined, autoRelogin?: boolean) => Promise<ApiResults<T>>;
13
+ _apiRequestRaw: (url: string | Request, options?: RequestInit & {
14
+ headers?: Record<string, string>;
15
+ } | undefined) => Promise<Response>;
16
+ streamURL: (url: string) => Promise<IncomingMessage>;
12
17
  }