@jd-data-limited/easy-fm 3.2.7 → 3.2.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.
Files changed (64) hide show
  1. package/package.json +10 -6
  2. package/src/FMError.d.ts +1 -1
  3. package/src/FMError.js +5 -0
  4. package/src/FMError.ts +1 -1
  5. package/src/connection/FMHost.d.ts +1 -1
  6. package/src/connection/FMHost.js +36 -45
  7. package/src/connection/FMHost.ts +3 -3
  8. package/src/connection/database.d.ts +5 -3
  9. package/src/connection/database.js +90 -112
  10. package/src/connection/database.ts +70 -82
  11. package/src/connection/databaseBase.d.ts +4 -1
  12. package/src/connection/databaseBase.ts +4 -1
  13. package/src/layouts/layout.d.ts +3 -3
  14. package/src/layouts/layout.js +26 -50
  15. package/src/layouts/layout.ts +22 -39
  16. package/src/layouts/layoutBase.d.ts +2 -2
  17. package/src/layouts/layoutBase.ts +2 -1
  18. package/src/layouts/layoutRecordManager.d.ts +1 -1
  19. package/src/layouts/layoutRecordManager.js +16 -31
  20. package/src/layouts/layoutRecordManager.ts +14 -30
  21. package/src/models/apiResults.d.ts +103 -0
  22. package/src/models/apiResults.js +28 -0
  23. package/src/models/apiResults.ts +112 -0
  24. package/src/records/field.d.ts +3 -2
  25. package/src/records/field.js +44 -68
  26. package/src/records/field.ts +46 -64
  27. package/src/records/getOperations/find.js +18 -38
  28. package/src/records/getOperations/find.ts +17 -28
  29. package/src/records/getOperations/recordGetOperation.js +6 -3
  30. package/src/records/getOperations/recordGetRange.js +16 -34
  31. package/src/records/getOperations/recordGetRange.ts +17 -24
  32. package/src/records/layoutRecord.js +103 -151
  33. package/src/records/layoutRecord.ts +101 -133
  34. package/src/records/portal.js +3 -0
  35. package/src/records/portalRecord.js +2 -1
  36. package/src/records/recordBase.js +6 -1
  37. package/src/types.d.ts +0 -4
  38. package/src/types.ts +0 -5
  39. package/.expo/README.md +0 -15
  40. package/.expo/settings.json +0 -8
  41. package/.idea/compiler.xml +0 -7
  42. package/.idea/copyright/JD_Data.xml +0 -6
  43. package/.idea/copyright/profiles_settings.xml +0 -7
  44. package/.idea/discord.xml +0 -7
  45. package/.idea/easy-fm.iml +0 -8
  46. package/.idea/jsLibraryMappings.xml +0 -6
  47. package/.idea/modules.xml +0 -8
  48. package/.idea/php.xml +0 -18
  49. package/.idea/vcs.xml +0 -6
  50. package/decs.d.ts +0 -7
  51. package/dist/index.min.js +0 -2
  52. package/dist/index.min.js.LICENSE.txt +0 -3
  53. package/docs/classes/FileMakerConnection.md +0 -86
  54. package/docs/classes/layout.md +0 -0
  55. package/examples/test.js +0 -7
  56. package/fmErrors.json +0 -1010
  57. package/img.png +0 -0
  58. package/test.d.ts +0 -2
  59. package/test.js +0 -30
  60. package/test.ts +0 -33
  61. package/test2.d.ts +0 -1
  62. package/test2.js +0 -15
  63. package/tsconfig.json +0 -16
  64. package/webpack.config.cjs +0 -20
package/package.json CHANGED
@@ -1,13 +1,12 @@
1
1
  {
2
2
  "name": "@jd-data-limited/easy-fm",
3
- "version": "3.2.7",
3
+ "version": "3.2.8",
4
4
  "dependencies": {
5
5
  "@types/btoa": "^1.2.3",
6
6
  "@types/events": "^3.0.0",
7
+ "follow-redirects": "^1.15.3",
7
8
  "moment": "^2.29.4",
8
- "node-fetch": "3.2.8",
9
- "typescript": "^5.2.2",
10
- "ytdl-core": "^4.11.4"
9
+ "node-fetch": "3.3.2"
11
10
  },
12
11
  "type": "module",
13
12
  "main": "src/index.js",
@@ -16,8 +15,11 @@
16
15
  "url": "https://github.com/JD-Data-Limited/easy-fm",
17
16
  "license": "SEE LICENSE IN LICENSE",
18
17
  "description": "easy-fm is a Node.js module that allows you to interact with\r a [FileMaker database stored](https://www.claris.com/filemaker/) on\r a [FileMaker server](https://www.claris.com/filemaker/server/). This module interacts with your server using the\r [FileMaker Data API](https://help.claris.com/en/data-api-guide/content/index.html).",
18
+ "files": [
19
+ "./src/**/*"
20
+ ],
19
21
  "scripts": {
20
- "test": "echo \"Error: no test specified\" && exit 1",
22
+ "test": "node --test tests/login.js",
21
23
  "build": "webpack"
22
24
  },
23
25
  "repository": {
@@ -34,7 +36,9 @@
34
36
  "url": "https://github.com/JD-Data-Limited/easy-fm/issues"
35
37
  },
36
38
  "devDependencies": {
39
+ "typescript": "^5.2.2",
37
40
  "webpack": "^5.81.0",
38
- "webpack-cli": "^5.0.2"
41
+ "webpack-cli": "^5.0.2",
42
+ "dotenv": "^16.3.1"
39
43
  }
40
44
  }
package/src/FMError.d.ts CHANGED
@@ -8,5 +8,5 @@ export declare class FMError extends Error {
8
8
  readonly code: number;
9
9
  readonly message: string;
10
10
  readonly messages: string;
11
- constructor(code: any, httpStatus: any, res: any, trace?: Error);
11
+ constructor(code: string | number, httpStatus: number, res: any, trace?: Error);
12
12
  }
package/src/FMError.js CHANGED
@@ -1013,6 +1013,11 @@ export const errs = [
1013
1013
  }
1014
1014
  ];
1015
1015
  export class FMError extends Error {
1016
+ httpStatus;
1017
+ res;
1018
+ code;
1019
+ message;
1020
+ messages;
1016
1021
  constructor(code, httpStatus, res, trace) {
1017
1022
  if (typeof code === "string")
1018
1023
  code = parseInt(code);
package/src/FMError.ts CHANGED
@@ -1021,7 +1021,7 @@ export class FMError extends Error {
1021
1021
  readonly message: string
1022
1022
  readonly messages: string
1023
1023
 
1024
- constructor(code, httpStatus, res, trace?: Error) {
1024
+ constructor(code: string | number, httpStatus: number, res: any, trace?: Error) {
1025
1025
  if (typeof code === "string") code = parseInt(code)
1026
1026
  super(errs.find(err => err.e === code).d || "Unknown error");
1027
1027
  this.httpStatus = httpStatus
@@ -9,5 +9,5 @@ export default class FMHost implements HostBase {
9
9
  constructor(_hostname: string, timezoneOffset?: number, verify?: boolean);
10
10
  listDatabases(credentials?: loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris): Promise<any>;
11
11
  database<T extends DatabaseStructure>(data: databaseOptionsWithExternalSources): Database<T>;
12
- getMetadata(): Promise<any>;
12
+ getMetadata(): Promise<FMHostMetadata>;
13
13
  }
@@ -1,20 +1,15 @@
1
1
  /*
2
2
  * Copyright (c) 2023. See LICENSE file for more information
3
3
  */
4
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
5
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
6
- return new (P || (P = Promise))(function (resolve, reject) {
7
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
8
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
9
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
10
- step((generator = generator.apply(thisArg, _arguments || [])).next());
11
- });
12
- };
13
4
  import { generateAuthorizationHeaders } from "./generateAuthorizationHeaders.js";
14
5
  import fetch from "node-fetch";
15
6
  import { FMError } from "../FMError.js";
16
7
  import { Database } from "./database.js";
17
8
  export default class FMHost {
9
+ hostname;
10
+ timezoneOffset;
11
+ verify;
12
+ metadata;
18
13
  constructor(_hostname, timezoneOffset = 0 - (new Date()).getTimezoneOffset(), verify = true) {
19
14
  if (!(/^https?:\/\//).test(_hostname))
20
15
  throw "hostname MUST begin with either http:// or https://";
@@ -22,47 +17,43 @@ export default class FMHost {
22
17
  this.timezoneOffset = timezoneOffset;
23
18
  this.verify = verify;
24
19
  }
25
- listDatabases(credentials) {
26
- return __awaiter(this, void 0, void 0, function* () {
27
- let headers = {};
28
- if (credentials) {
29
- headers = generateAuthorizationHeaders(credentials);
30
- }
31
- let _fetch = yield fetch(`${this.hostname}/fmi/data/v2/databases`, {
32
- method: "GET",
33
- headers
34
- });
35
- let data = yield _fetch.json();
36
- // console.log(data.messages[0])
37
- if (data.messages[0].code === "0") {
38
- return data.response.databases;
39
- }
40
- else {
41
- // @ts-ignore
42
- throw new FMError(data.messages[0].code, data.status, data);
43
- }
20
+ async listDatabases(credentials) {
21
+ let headers = {};
22
+ if (credentials) {
23
+ headers = generateAuthorizationHeaders(credentials);
24
+ }
25
+ let _fetch = await fetch(`${this.hostname}/fmi/data/v2/databases`, {
26
+ method: "GET",
27
+ headers
44
28
  });
29
+ let data = await _fetch.json();
30
+ // console.log(data.messages[0])
31
+ if (data.messages[0].code === "0") {
32
+ return data.response.databases;
33
+ }
34
+ else {
35
+ // @ts-ignore
36
+ throw new FMError(data.messages[0].code, data.status, data);
37
+ }
45
38
  }
46
39
  database(data) {
47
40
  return new Database(this, data);
48
41
  }
49
- getMetadata() {
50
- return __awaiter(this, void 0, void 0, function* () {
51
- if (this.metadata)
52
- return this.metadata;
53
- let _fetch = yield fetch(`${this.hostname}/fmi/data/v2/productInfo`, {
54
- method: "GET",
55
- });
56
- let data = yield _fetch.json();
57
- // console.log(data.messages[0])
58
- if (data.messages[0].code === "0") {
59
- this.metadata = data.response;
60
- return data.response;
61
- }
62
- else {
63
- // @ts-ignore
64
- throw new FMError(data.messages[0].code, data.status, data);
65
- }
42
+ async getMetadata() {
43
+ if (this.metadata)
44
+ return this.metadata;
45
+ let _fetch = await fetch(`${this.hostname}/fmi/data/v2/productInfo`, {
46
+ method: "GET",
66
47
  });
48
+ let data = await _fetch.json();
49
+ // console.log(data.messages[0])
50
+ if (data.messages[0].code === "0") {
51
+ this.metadata = data.response;
52
+ return data.response;
53
+ }
54
+ else {
55
+ // @ts-ignore
56
+ throw new FMError(data.messages[0].code, data.status, data);
57
+ }
67
58
  }
68
59
  }
@@ -9,12 +9,12 @@ import {Database} from "./database.js";
9
9
  import {HostBase} from "./HostBase.js"
10
10
  import {
11
11
  databaseOptionsWithExternalSources, DatabaseStructure,
12
- fileMakerResponse,
13
12
  FMHostMetadata,
14
13
  loginOptionsClaris,
15
14
  loginOptionsFileMaker,
16
15
  loginOptionsOAuth
17
16
  } from "../types.js";
17
+ import {ApiResults} from "../models/apiResults";
18
18
 
19
19
  export default class FMHost implements HostBase {
20
20
  readonly hostname: string
@@ -39,7 +39,7 @@ export default class FMHost implements HostBase {
39
39
  method: "GET",
40
40
  headers
41
41
  })
42
- let data = await _fetch.json() as fileMakerResponse
42
+ let data = await _fetch.json() as ApiResults<{databases: any}>
43
43
  // console.log(data.messages[0])
44
44
 
45
45
  if (data.messages[0].code === "0") {
@@ -61,7 +61,7 @@ export default class FMHost implements HostBase {
61
61
  let _fetch = await fetch(`${this.hostname}/fmi/data/v2/productInfo`, {
62
62
  method: "GET",
63
63
  })
64
- let data = await _fetch.json() as fileMakerResponse
64
+ let data = await _fetch.json() as ApiResults<FMHostMetadata>
65
65
  // console.log(data.messages[0])
66
66
 
67
67
  if (data.messages[0].code === "0") {
@@ -7,6 +7,7 @@ import HTTP_REDIRECT from "follow-redirects";
7
7
  import { databaseOptionsWithExternalSources, DatabaseStructure, Script } from "../types.js";
8
8
  import { HostBase } from "./HostBase.js";
9
9
  import { DatabaseBase } from "./databaseBase";
10
+ import { ApiResults } from "../models/apiResults";
10
11
  export declare class Database<T extends DatabaseStructure> extends EventEmitter implements DatabaseBase {
11
12
  private _token;
12
13
  readonly host: HostBase;
@@ -16,14 +17,15 @@ export declare class Database<T extends DatabaseStructure> extends EventEmitter
16
17
  constructor(host: HostBase, conn: databaseOptionsWithExternalSources);
17
18
  private generateExternalSourceLogin;
18
19
  logout(): Promise<void>;
19
- login(): Promise<string>;
20
+ login(): Promise<any>;
20
21
  get token(): any;
21
22
  get endpoint(): string;
22
- apiRequest<T = any>(url: string | Request, options?: any, autoRelogin?: boolean): Promise<T>;
23
+ apiRequest<T = any>(url: string | Request, options?: any, autoRelogin?: boolean): Promise<ApiResults<T>>;
24
+ listLayouts(): Promise<Layout<LayoutInterface>[]>;
23
25
  getLayout<R extends string>(name: R): Layout<T["layouts"][R]>;
24
26
  getLayout<R extends LayoutInterface>(name: string): Layout<R>;
25
27
  setGlobals(globalFields: any): Promise<void>;
26
28
  script(name: any, parameter?: string): Script;
27
29
  _tokenExpired(): void;
28
- streamContainer(field: any, url: any): Promise<HTTP_REDIRECT.http.IncomingMessage>;
30
+ streamURL(url: string): Promise<HTTP_REDIRECT.http.IncomingMessage>;
29
31
  }
@@ -1,15 +1,6 @@
1
1
  /*
2
2
  * Copyright (c) 2023. See LICENSE file for more information
3
3
  */
4
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
5
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
6
- return new (P || (P = Promise))(function (resolve, reject) {
7
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
8
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
9
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
10
- step((generator = generator.apply(thisArg, _arguments || [])).next());
11
- });
12
- };
13
4
  import { EventEmitter } from "events";
14
5
  import fetch from "node-fetch";
15
6
  import { generateAuthorizationHeaders } from "./generateAuthorizationHeaders.js";
@@ -17,9 +8,13 @@ import { FMError } from "../FMError.js";
17
8
  import { Layout } from "../layouts/layout.js";
18
9
  import HTTP_REDIRECT from "follow-redirects";
19
10
  export class Database extends EventEmitter {
11
+ _token;
12
+ host;
13
+ connection_details;
14
+ cookies = {};
15
+ name;
20
16
  constructor(host, conn) {
21
17
  super();
22
- this.cookies = {};
23
18
  this.host = host;
24
19
  this.name = conn.database;
25
20
  this.connection_details = conn;
@@ -37,66 +32,53 @@ export class Database extends EventEmitter {
37
32
  throw "Not yet supported login method";
38
33
  }
39
34
  }
40
- logout() {
41
- return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
42
- if (!this.token)
43
- reject(new Error("Not logged in"));
44
- let _fetch = yield fetch(`${this.endpoint}/sessions/${this.token}`, {
45
- method: "DELETE",
46
- headers: {
47
- "content-type": "application/json"
48
- }
49
- });
50
- let data = yield _fetch.json();
51
- // console.log(data)
52
- this._token = null;
53
- resolve();
54
- }));
55
- }
56
- login() {
57
- return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
58
- // Reset cookies
59
- this.cookies = {};
60
- try {
61
- yield this.host.getMetadata();
62
- }
63
- catch (e) {
64
- reject(e);
65
- return;
35
+ async logout() {
36
+ if (!this.token)
37
+ throw new Error("Not logged in");
38
+ let _fetch = await fetch(`${this.endpoint}/sessions/${this.token}`, {
39
+ method: "DELETE",
40
+ headers: {
41
+ "content-type": "application/json"
66
42
  }
67
- if (this.token)
68
- throw new Error("Already logged in. Run logout() first");
69
- if (this.connection_details.credentials.method === "token") {
70
- this._token = this.connection_details.credentials.token;
71
- resolve(this.token);
43
+ });
44
+ let data = await _fetch.json();
45
+ // console.log(data)
46
+ this._token = null;
47
+ }
48
+ async login() {
49
+ // Reset cookies
50
+ this.cookies = {};
51
+ await this.host.getMetadata();
52
+ if (this.token)
53
+ throw new Error("Already logged in. Run logout() first");
54
+ if (this.connection_details.credentials.method === "token") {
55
+ this._token = this.connection_details.credentials.token;
56
+ return this.token;
57
+ }
58
+ else {
59
+ console.time();
60
+ let res = await fetch(`${this.endpoint}/sessions`, {
61
+ hostname: this.host.hostname,
62
+ port: 443,
63
+ method: "POST",
64
+ headers: generateAuthorizationHeaders(this.connection_details.credentials),
65
+ body: JSON.stringify({
66
+ fmDataSource: this.connection_details.externalSources.map(i => {
67
+ let _i = i;
68
+ return this.generateExternalSourceLogin(_i);
69
+ })
70
+ })
71
+ });
72
+ console.time();
73
+ let _res = (await res.json());
74
+ if (res.status === 200) {
75
+ this._token = res.headers.get('x-fm-data-access-token');
76
+ return this._token;
72
77
  }
73
78
  else {
74
- fetch(`${this.endpoint}/sessions`, {
75
- hostname: this.host.hostname,
76
- port: 443,
77
- method: "POST",
78
- headers: generateAuthorizationHeaders(this.connection_details.credentials),
79
- body: JSON.stringify({
80
- fmDataSource: this.connection_details.externalSources.map(i => {
81
- let _i = i;
82
- return this.generateExternalSourceLogin(_i);
83
- })
84
- })
85
- }).then((res) => __awaiter(this, void 0, void 0, function* () {
86
- let _res = (yield res.json());
87
- if (res.status === 200) {
88
- this._token = res.headers.get('x-fm-data-access-token');
89
- resolve(this._token);
90
- }
91
- else {
92
- reject(new FMError(_res.messages[0].code, _res.status, res));
93
- }
94
- }))
95
- .catch(e => {
96
- reject(e);
97
- });
79
+ throw new FMError(_res.messages[0].code, _res.status, res);
98
80
  }
99
- }));
81
+ }
100
82
  }
101
83
  get token() {
102
84
  return this._token;
@@ -104,51 +86,52 @@ export class Database extends EventEmitter {
104
86
  get endpoint() {
105
87
  return `${this.host.hostname}/fmi/data/v2/databases/${this.name}`;
106
88
  }
107
- apiRequest(url, options = {}, autoRelogin = true) {
108
- return __awaiter(this, void 0, void 0, function* () {
109
- if (!options.headers)
110
- options.headers = {};
111
- options.headers["content-type"] = options.headers["content-type"] ? options.headers["content-type"] : "application/json";
112
- options.headers["authorization"] = "Bearer " + this._token;
113
- options.rejectUnauthorized = this.host.verify;
114
- let _fetch = yield fetch(url, options);
115
- if (_fetch.headers.get('set-cookie')) {
116
- for (let cookie of _fetch.headers.get('set-cookie')) {
117
- let cookie_split = cookie.split("=");
118
- this.cookies[cookie_split[0]] = cookie_split[1];
119
- }
120
- }
121
- let data = yield _fetch.json();
122
- // console.log(data.messages[0])
123
- if (data.messages[0].code == 952 && autoRelogin) {
124
- this._token = null;
125
- yield this.login();
126
- return yield this.apiRequest(url, options, false);
89
+ async apiRequest(url, options = {}, autoRelogin = true) {
90
+ if (!options.headers)
91
+ options.headers = {};
92
+ options.headers["content-type"] = options.headers["content-type"] ? options.headers["content-type"] : "application/json";
93
+ options.headers["authorization"] = "Bearer " + this._token;
94
+ options.rejectUnauthorized = this.host.verify;
95
+ let _fetch = await fetch(url, options);
96
+ if (_fetch.headers.get('set-cookie')) {
97
+ for (let cookie of _fetch.headers.get('set-cookie')) {
98
+ let cookie_split = cookie.split("=");
99
+ this.cookies[cookie_split[0]] = cookie_split[1];
127
100
  }
128
- return data;
129
- });
101
+ }
102
+ let data = await _fetch.json();
103
+ // Remove response if it is empty. This makes checking for an empty response easier
104
+ if (Object.keys(data.response).length === 0)
105
+ delete data.response;
106
+ // console.log(data.messages[0])
107
+ if (data.messages[0].code === "952" && autoRelogin) {
108
+ this._token = null;
109
+ await this.login();
110
+ return await this.apiRequest(url, options, false);
111
+ }
112
+ else if (data.messages[0].code !== "0") {
113
+ throw new FMError(data.messages[0].code, _fetch.status, data);
114
+ }
115
+ data.httpStatus = _fetch.status;
116
+ return data;
117
+ }
118
+ async listLayouts() {
119
+ let req = await this.apiRequest(`${this.endpoint}/layouts`);
120
+ if (!req.response)
121
+ throw new FMError(req.messages[0].code, req.httpStatus, req.messages[0].message);
122
+ return req.response.layouts.filter(i => !i.isFolder).map(layout => new Layout(this, layout.name));
130
123
  }
131
124
  getLayout(name) {
132
125
  return new Layout(this, name);
133
126
  }
134
- setGlobals(globalFields) {
127
+ /*
128
+ @deprecated
129
+ */
130
+ async setGlobals(globalFields) {
135
131
  // console.log({globalFields})
136
- return new Promise((resolve, reject) => {
137
- this.apiRequest(`${this.endpoint}/globals`, {
138
- method: "PATCH",
139
- body: JSON.stringify({ globalFields })
140
- }).then(res => {
141
- // console.log(res)
142
- if (res.messages[0].code === "0") {
143
- resolve();
144
- }
145
- else {
146
- reject((res.messages[0].code, res.status, res));
147
- }
148
- })
149
- .catch(e => {
150
- reject(e);
151
- });
132
+ await this.apiRequest(`${this.endpoint}/globals`, {
133
+ method: "PATCH",
134
+ body: JSON.stringify({ globalFields })
152
135
  });
153
136
  }
154
137
  script(name, parameter = "") {
@@ -157,15 +140,10 @@ export class Database extends EventEmitter {
157
140
  _tokenExpired() {
158
141
  this.emit("token_expired");
159
142
  }
160
- streamContainer(field, url) {
143
+ streamURL(url) {
161
144
  return new Promise((resolve, reject) => {
162
- if (field.metadata.result !== "container") {
163
- reject("Cannot stream the field " + field.id + " as it is not a container");
164
- return;
165
- }
166
145
  if (!url || typeof url !== "string") {
167
- reject("Container is empty, or has invalid value");
168
- return;
146
+ throw new Error("URL is empty, or has invalid value");
169
147
  }
170
148
  let headers = {};
171
149
  if (Object.keys(this.cookies).length !== 0) {