@jd-data-limited/easy-fm 4.0.4 → 4.0.7

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 (53) hide show
  1. package/package.json +7 -2
  2. package/src/FMError.d.ts +0 -1
  3. package/src/FMError.js +0 -1
  4. package/src/FMError.ts +0 -1
  5. package/src/bin/cli.d.ts +2 -0
  6. package/src/bin/cli.js +19 -0
  7. package/src/bin/cli.ts +22 -0
  8. package/src/bin/example.xml +0 -0
  9. package/src/bin/generateTypes.d.ts +1 -0
  10. package/src/bin/generateTypes.js +250 -0
  11. package/src/bin/generateTypes.ts +186 -0
  12. package/src/connection/FMHost.js +12 -3
  13. package/src/connection/FMHost.ts +12 -3
  14. package/src/connection/database.d.ts +2 -1
  15. package/src/connection/database.js +10 -6
  16. package/src/connection/database.ts +11 -7
  17. package/src/connection/databaseBase.d.ts +2 -1
  18. package/src/connection/databaseBase.ts +2 -1
  19. package/src/index.d.ts +2 -1
  20. package/src/index.js +2 -1
  21. package/src/index.ts +4 -1
  22. package/src/layouts/layout.d.ts +1 -1
  23. package/src/layouts/layout.js +9 -5
  24. package/src/layouts/layout.ts +7 -5
  25. package/src/layouts/layoutRecordManager.js +3 -3
  26. package/src/layouts/layoutRecordManager.ts +3 -3
  27. package/src/models/apiResults.d.ts +1 -1
  28. package/src/models/apiResults.ts +1 -1
  29. package/src/records/fieldBase.js +3 -1
  30. package/src/records/fieldBase.ts +2 -1
  31. package/src/records/getOperations/recordGetOperation.d.ts +2 -1
  32. package/src/records/getOperations/recordGetOperation.js +33 -26
  33. package/src/records/getOperations/recordGetOperation.ts +39 -33
  34. package/src/records/layoutRecord.d.ts +3 -2
  35. package/src/records/layoutRecord.js +58 -18
  36. package/src/records/layoutRecord.ts +61 -19
  37. package/src/records/portal.d.ts +1 -1
  38. package/src/records/portal.js +3 -3
  39. package/src/records/portal.ts +3 -3
  40. package/src/records/portalRecord.d.ts +3 -2
  41. package/src/records/portalRecord.js +2 -4
  42. package/src/records/portalRecord.ts +7 -11
  43. package/src/records/recordBase.d.ts +3 -4
  44. package/src/records/recordBase.js +3 -32
  45. package/src/records/recordBase.ts +5 -37
  46. package/src/utils/fmQuery.d.ts +4 -0
  47. package/src/utils/fmQuery.js +20 -0
  48. package/src/utils/query.d.ts +4 -0
  49. package/src/utils/query.js +20 -0
  50. package/src/utils/query.ts +25 -0
  51. package/src/records/field.d.ts +0 -5
  52. package/src/records/field.js +0 -7
  53. package/src/records/field.ts +0 -10
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@jd-data-limited/easy-fm",
3
- "version": "4.0.4",
3
+ "version": "4.0.7",
4
4
  "dependencies": {
5
5
  "follow-redirects": "^1.15.5",
6
+ "@types/follow-redirects": "^1.14.4",
6
7
  "moment": "^2.29.4"
7
8
  },
8
9
  "type": "module",
@@ -35,8 +36,9 @@
35
36
  "devDependencies": {
36
37
  "@types/btoa": "^1.2.3",
37
38
  "@types/events": "^3.0.0",
38
- "@types/follow-redirects": "^1.14.4",
39
+ "@types/inquirer": "^9.0.7",
39
40
  "dotenv": "^16.3.1",
41
+ "inquirer": "^9.2.13",
40
42
  "ts-morph": "^21.0.1",
41
43
  "typescript": "^5.3.3",
42
44
  "webpack": "^5.81.0",
@@ -44,5 +46,8 @@
44
46
  },
45
47
  "engines": {
46
48
  "node": ">=20.9.0"
49
+ },
50
+ "bin": {
51
+ "easyfm": "src/bin/cli.js"
47
52
  }
48
53
  }
package/src/FMError.d.ts CHANGED
@@ -6,7 +6,6 @@ export declare class FMError extends Error {
6
6
  readonly httpStatus: number;
7
7
  readonly res: any;
8
8
  readonly code: number;
9
- readonly message: string;
10
9
  readonly messages: string;
11
10
  constructor(code: string | number, httpStatus: number, res: any, trace?: Error);
12
11
  }
package/src/FMError.js CHANGED
@@ -1016,7 +1016,6 @@ export class FMError extends Error {
1016
1016
  httpStatus;
1017
1017
  res;
1018
1018
  code;
1019
- message;
1020
1019
  messages;
1021
1020
  constructor(code, httpStatus, res, trace) {
1022
1021
  if (typeof code === "string")
package/src/FMError.ts CHANGED
@@ -1018,7 +1018,6 @@ export class FMError extends Error {
1018
1018
  readonly httpStatus: number
1019
1019
  readonly res: any
1020
1020
  readonly code: number
1021
- readonly message: string
1022
1021
  readonly messages: string
1023
1022
 
1024
1023
  constructor(code: string | number, httpStatus: number, res: any, trace?: Error) {
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/src/bin/cli.js ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /*
4
+ * Copyright (c) 2024. See LICENSE file for more information
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ var commander_1 = require("commander");
8
+ var generateTypes_js_1 = require("./generateTypes.js");
9
+ var program = new commander_1.Command();
10
+ program
11
+ .name("easyfm")
12
+ .description("A NodeJS wrapper for the FileMaker Data CLI");
13
+ program
14
+ .command("generate-types")
15
+ .description("Automatically generate layout interfaces from your database")
16
+ .action(function () {
17
+ (0, generateTypes_js_1.generateTypesCLI)();
18
+ });
19
+ program.parse(process.argv);
package/src/bin/cli.ts ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Copyright (c) 2024. See LICENSE file for more information
4
+ */
5
+
6
+ import {Command} from "commander"
7
+ import {generateTypesCLI} from "./generateTypes.js";
8
+
9
+ const program = new Command()
10
+
11
+ program
12
+ .name("easyfm")
13
+ .description("A NodeJS wrapper for the FileMaker Data CLI")
14
+
15
+ program
16
+ .command("generate-types")
17
+ .description("Automatically generate layout interfaces from your database")
18
+ .action(() => {
19
+ generateTypesCLI()
20
+ })
21
+
22
+ program.parse(process.argv)
Binary file
@@ -0,0 +1 @@
1
+ export declare function generateTypesCLI(): Promise<void>;
@@ -0,0 +1,250 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) 2024. See LICENSE file for more information
4
+ */
5
+ var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
6
+ if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
7
+ return cooked;
8
+ };
9
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
10
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
11
+ return new (P || (P = Promise))(function (resolve, reject) {
12
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
13
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
14
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
15
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
16
+ });
17
+ };
18
+ var __generator = (this && this.__generator) || function (thisArg, body) {
19
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
20
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
21
+ function verb(n) { return function (v) { return step([n, v]); }; }
22
+ function step(op) {
23
+ if (f) throw new TypeError("Generator is already executing.");
24
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
25
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
26
+ if (y = 0, t) op = [op[0] & 2, t.value];
27
+ switch (op[0]) {
28
+ case 0: case 1: t = op; break;
29
+ case 4: _.label++; return { value: op[1], done: false };
30
+ case 5: _.label++; y = op[1]; op = [0]; continue;
31
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
32
+ default:
33
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
34
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
35
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
36
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
37
+ if (t[2]) _.ops.pop();
38
+ _.trys.pop(); continue;
39
+ }
40
+ op = body.call(thisArg, _);
41
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
42
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
43
+ }
44
+ };
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.generateTypesCLI = void 0;
47
+ var apiResults_js_1 = require("../models/apiResults.js");
48
+ var inquirer_1 = require("inquirer");
49
+ var FMHost_js_1 = require("../connection/FMHost.js");
50
+ var fs = require("fs");
51
+ function sub(str) {
52
+ return str
53
+ .replace(/[^a-zA-Z0-9_]/g, "") // Remove non-alphanumeric characters
54
+ .replace(/^[0-9]+/g, ''); // Removing leading numbers
55
+ }
56
+ function generateFieldType(field) {
57
+ switch (field.result) {
58
+ case apiResults_js_1.ApiFieldResultTypes.TEXT:
59
+ return "FieldBase<string>";
60
+ case apiResults_js_1.ApiFieldResultTypes.CONTAINER:
61
+ return "FieldBase<Container>";
62
+ case apiResults_js_1.ApiFieldResultTypes.DATE:
63
+ return "FieldBase<Date>";
64
+ case apiResults_js_1.ApiFieldResultTypes.NUMBER:
65
+ return "FieldBase<number>";
66
+ case apiResults_js_1.ApiFieldResultTypes.TIME:
67
+ return "FieldBase<Date>";
68
+ case apiResults_js_1.ApiFieldResultTypes.TIMESTAMP:
69
+ return "FieldBase<Date>";
70
+ }
71
+ }
72
+ function processFieldsChunk(fields) {
73
+ var existing_fields = [];
74
+ fields = fields.filter(function (field) {
75
+ if (existing_fields.includes(field.name))
76
+ return false;
77
+ existing_fields.push(field.name);
78
+ return true;
79
+ });
80
+ return fields.map(function (field) {
81
+ return JSON.stringify(field.name) + ": " + generateFieldType(field);
82
+ });
83
+ }
84
+ function camelToSnakeCase(str) {
85
+ return str.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();
86
+ }
87
+ function safeStringInjection(str) {
88
+ var args = [];
89
+ for (var _i = 1; _i < arguments.length; _i++) {
90
+ args[_i - 1] = arguments[_i];
91
+ }
92
+ var result = "";
93
+ for (var i = 0; i < str.length; i++) {
94
+ result += str[i];
95
+ if (i < args.length) {
96
+ result += JSON.stringify(args[i].toString());
97
+ }
98
+ }
99
+ return result;
100
+ }
101
+ function generateTypesCLI() {
102
+ return __awaiter(this, void 0, void 0, function () {
103
+ var data, HOST, DATABASE, stream, layouts, layoutInterfaces, _loop_1, _i, layouts_1, layout, layoutInterfaceLinks, _a, layoutInterfaces_1, layout, _b, layoutInterfaces_2, layout;
104
+ return __generator(this, function (_c) {
105
+ switch (_c.label) {
106
+ case 0:
107
+ process.stdout.write('\x1Bc');
108
+ console.log("For this tool to work, you are required to have access to an account on the target database.\n" +
109
+ "This account must have the fmrest extended privilege enabled.\n" +
110
+ "This tool can only export type interfaces for layouts that the given account has access to.");
111
+ console.log("\n");
112
+ return [4 /*yield*/, inquirer_1.default.prompt([
113
+ {
114
+ type: "input",
115
+ name: "hostname",
116
+ message: "The IP address/domain of your FileMaker server (must start with either http:// or https://)",
117
+ validate: function (input) {
118
+ return input.startsWith("http://") || input.startsWith("https://") || "Please enter a valid IP address/domain starting with http:// or https://";
119
+ }
120
+ },
121
+ {
122
+ type: "number",
123
+ name: "timezoneOffset",
124
+ message: "Server timezone offset (minutes)",
125
+ default: 0 - (new Date()).getTimezoneOffset()
126
+ },
127
+ {
128
+ type: "confirm",
129
+ name: "verify",
130
+ default: true,
131
+ message: "Verify your server's identity",
132
+ when: function (previousAnswers) {
133
+ return previousAnswers.hostname.startsWith("https://");
134
+ }
135
+ },
136
+ {
137
+ type: "input",
138
+ name: "database",
139
+ message: "Database filename"
140
+ },
141
+ {
142
+ type: "input",
143
+ name: "username",
144
+ message: "Database username"
145
+ },
146
+ {
147
+ type: "password",
148
+ name: "password",
149
+ message: "Database password",
150
+ mask: "*"
151
+ }
152
+ ])];
153
+ case 1:
154
+ data = _c.sent();
155
+ HOST = new FMHost_js_1.default(data.hostname, data.timezoneOffset, data.verify);
156
+ DATABASE = HOST.database({
157
+ database: data.database,
158
+ credentials: {
159
+ method: "filemaker",
160
+ username: data.username,
161
+ password: data.password
162
+ },
163
+ externalSources: []
164
+ });
165
+ return [4 /*yield*/, DATABASE.login()
166
+ // Create file write stream
167
+ ];
168
+ case 2:
169
+ _c.sent();
170
+ stream = fs.createWriteStream("./types.ts");
171
+ return [4 /*yield*/, DATABASE.listLayouts()];
172
+ case 3:
173
+ layouts = _c.sent();
174
+ stream.write("import FMHost, {Container, FieldBase, LayoutInterface, Portal} from \"@jd-data-limited/easy-fm\";\n");
175
+ stream.write("// " + layouts.length + " layouts found\n\n");
176
+ layoutInterfaces = new Map();
177
+ _loop_1 = function (layout) {
178
+ var metadata, fields, portals, portalsProcessed, interfaceName;
179
+ return __generator(this, function (_d) {
180
+ switch (_d.label) {
181
+ case 0: return [4 /*yield*/, layout.getLayoutMeta()];
182
+ case 1:
183
+ metadata = _d.sent();
184
+ fields = processFieldsChunk(metadata.fieldMetaData);
185
+ portals = metadata.portalMetaData;
186
+ portalsProcessed = Object.keys(portals).map(function (portalName) {
187
+ return "".concat(JSON.stringify(portalName), ": Portal<{").concat(processFieldsChunk(portals[portalName]), "}>");
188
+ });
189
+ interfaceName = sub(layout.name) + "LayoutInterface";
190
+ layoutInterfaces.set(layout.name, {
191
+ interfaceName: interfaceName,
192
+ interface: "export interface ".concat(interfaceName, " extends LayoutInterface {\n fields: {").concat(fields.join(",\n "), "},\n portals: {").concat(portalsProcessed.join(",\n "), "}\n }")
193
+ });
194
+ return [2 /*return*/];
195
+ }
196
+ });
197
+ };
198
+ _i = 0, layouts_1 = layouts;
199
+ _c.label = 4;
200
+ case 4:
201
+ if (!(_i < layouts_1.length)) return [3 /*break*/, 7];
202
+ layout = layouts_1[_i];
203
+ return [5 /*yield**/, _loop_1(layout)];
204
+ case 5:
205
+ _c.sent();
206
+ _c.label = 6;
207
+ case 6:
208
+ _i++;
209
+ return [3 /*break*/, 4];
210
+ case 7:
211
+ layoutInterfaceLinks = [];
212
+ for (_a = 0, layoutInterfaces_1 = layoutInterfaces; _a < layoutInterfaces_1.length; _a++) {
213
+ layout = layoutInterfaces_1[_a];
214
+ layoutInterfaceLinks.push([
215
+ "".concat(JSON.stringify(layout[0]), ": ").concat(layout[1].interfaceName),
216
+ "".concat(sub(camelToSnakeCase(layout[0]).toUpperCase().replace(" ", "_")), ": HOST.getLayout<").concat(layout[1].interfaceName, ">(").concat(JSON.stringify(layout[0]), ")")
217
+ ]);
218
+ }
219
+ stream.write(safeStringInjection(templateObject_1 || (templateObject_1 = __makeTemplateObject(["const HOST = new FMHost(", ", ", ", ", ");"], ["const HOST = new FMHost(", ", ", ", ", ");"])), data.hostname, data.timezoneOffset, data.verify));
220
+ stream.write("HOST.database<{layouts:{".concat(layoutInterfaceLinks.map(function (i) { return i[0]; }).join(","), "}}>();"));
221
+ stream.write("\n\n");
222
+ stream.write("const LAYOUTS = {".concat(layoutInterfaceLinks.map(function (i) { return i[1]; }).join(","), "};"));
223
+ for (_b = 0, layoutInterfaces_2 = layoutInterfaces; _b < layoutInterfaces_2.length; _b++) {
224
+ layout = layoutInterfaces_2[_b];
225
+ stream.write("\n\n");
226
+ stream.write(layout[1].interface + ";");
227
+ }
228
+ // StructureKind
229
+ //
230
+ // const project = new Project({
231
+ // tsConfigFilePath: "../tsconfig.json"
232
+ // })
233
+ // project.resolveSourceFileDependencies()
234
+ return [4 /*yield*/, DATABASE.logout()];
235
+ case 8:
236
+ // StructureKind
237
+ //
238
+ // const project = new Project({
239
+ // tsConfigFilePath: "../tsconfig.json"
240
+ // })
241
+ // project.resolveSourceFileDependencies()
242
+ _c.sent();
243
+ console.log("DONE! Wrote database types to ./types.ts");
244
+ return [2 /*return*/];
245
+ }
246
+ });
247
+ });
248
+ }
249
+ exports.generateTypesCLI = generateTypesCLI;
250
+ var templateObject_1;
@@ -0,0 +1,186 @@
1
+ /*
2
+ * Copyright (c) 2024. See LICENSE file for more information
3
+ */
4
+
5
+ import {ApiFieldMetadata, ApiFieldResultTypes} from "../models/apiResults.js";
6
+ import inquirer from 'inquirer';
7
+ import FMHost from "../connection/FMHost.js";
8
+ import * as fs from "fs";
9
+
10
+ function sub(str: string) {
11
+ return str
12
+ .replace(/[^a-zA-Z0-9_]/g, "") // Remove non-alphanumeric characters
13
+ .replace(/^[0-9]+/g, '') // Removing leading numbers
14
+ }
15
+
16
+ function generateFieldType(field: ApiFieldMetadata) {
17
+ switch (field.result) {
18
+ case ApiFieldResultTypes.TEXT:
19
+ return "FieldBase<string>"
20
+ case ApiFieldResultTypes.CONTAINER:
21
+ return "FieldBase<Container>"
22
+ case ApiFieldResultTypes.DATE:
23
+ return "FieldBase<Date>"
24
+ case ApiFieldResultTypes.NUMBER:
25
+ return "FieldBase<number>"
26
+ case ApiFieldResultTypes.TIME:
27
+ return "FieldBase<Date>"
28
+ case ApiFieldResultTypes.TIMESTAMP:
29
+ return "FieldBase<Date>"
30
+ }
31
+ }
32
+
33
+ function processFieldsChunk(fields: ApiFieldMetadata[]) {
34
+ let existing_fields: string[] = []
35
+ fields = fields.filter(field => {
36
+ if (existing_fields.includes(field.name)) return false
37
+ existing_fields.push(field.name)
38
+ return true
39
+ })
40
+
41
+ return fields.map(field => {
42
+ return JSON.stringify(field.name) + ": " + generateFieldType(field)
43
+ })
44
+ }
45
+
46
+ function camelToSnakeCase(str: string) {
47
+ return str.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();
48
+ }
49
+
50
+ function safeStringInjection(str: TemplateStringsArray, ...args: {toString(): string}[]) {
51
+ let result = "";
52
+ for (let i = 0; i < str.length; i++) {
53
+ result += str[i];
54
+ if (i < args.length) {
55
+ result += JSON.stringify(args[i].toString());
56
+ }
57
+ }
58
+ return result;
59
+ }
60
+
61
+ export async function generateTypesCLI() {
62
+ process.stdout.write('\x1Bc');
63
+ console.log("For this tool to work, you are required to have access to an account on the target database.\n" +
64
+ "This account must have the fmrest extended privilege enabled.\n" +
65
+ "This tool can only export type interfaces for layouts that the given account has access to.")
66
+ console.log("\n")
67
+
68
+ const data = await inquirer.prompt([
69
+ {
70
+ type: "input",
71
+ name: "hostname",
72
+ message: "The IP address/domain of your FileMaker server (must start with either http:// or https://)",
73
+ validate(input: any): boolean | string | Promise<boolean | string> {
74
+ return input.startsWith("http://") || input.startsWith("https://") || "Please enter a valid IP address/domain starting with http:// or https://";
75
+ }
76
+ },
77
+ {
78
+ type: "number",
79
+ name: "timezoneOffset",
80
+ message: "Server timezone offset (minutes)",
81
+ default: 0 - (new Date()).getTimezoneOffset()
82
+ },
83
+ {
84
+ type: "confirm",
85
+ name: "verify",
86
+ default: true,
87
+ message: "Verify your server's identity",
88
+ when(previousAnswers) {
89
+ return previousAnswers.hostname.startsWith("https://")
90
+ }
91
+ },
92
+ {
93
+ type: "input",
94
+ name: "database",
95
+ message: "Database filename"
96
+ },
97
+ {
98
+ type: "input",
99
+ name: "username",
100
+ message: "Database username"
101
+ },
102
+ {
103
+ type: "password",
104
+ name: "password",
105
+ message: "Database password",
106
+ mask: "*"
107
+ }
108
+ ])
109
+
110
+ const HOST = new FMHost(data.hostname, data.timezoneOffset, data.verify)
111
+ const DATABASE = HOST.database({
112
+ database: data.database,
113
+ credentials: {
114
+ method: "filemaker",
115
+ username: data.username,
116
+ password: data.password
117
+ },
118
+ externalSources: []
119
+ })
120
+
121
+ await DATABASE.login()
122
+ // Create file write stream
123
+ const stream = fs.createWriteStream("./types.ts")
124
+ let layouts = await DATABASE.listLayouts()
125
+
126
+ stream.write(`import FMHost, {Container, FieldBase, LayoutInterface, Portal} from "@jd-data-limited/easy-fm";\n`)
127
+ stream.write("// " + layouts.length + " layouts found\n\n")
128
+
129
+ let layoutInterfaces = new Map<string, {
130
+ interfaceName: string,
131
+ interface: string
132
+ }>()
133
+ for (let layout of layouts) {
134
+ let metadata = await layout.getLayoutMeta()
135
+ let fields = processFieldsChunk(metadata.fieldMetaData)
136
+
137
+ let portals = metadata.portalMetaData
138
+ let portalsProcessed = Object.keys(portals).map(portalName => {
139
+ return `${JSON.stringify(portalName)}: Portal<{${processFieldsChunk(
140
+ portals[portalName]
141
+ )}}>`
142
+ })
143
+
144
+ let interfaceName = sub(layout.name) + "LayoutInterface"
145
+ layoutInterfaces.set(layout.name, {
146
+ interfaceName,
147
+ interface: `export interface ${interfaceName} extends LayoutInterface {
148
+ fields: {${fields.join(",\n ")}},
149
+ portals: {${portalsProcessed.join(",\n ")}}
150
+ }`
151
+ })
152
+ }
153
+
154
+ let layoutInterfaceLinks: [string, string][] = []
155
+ for (let layout of layoutInterfaces) {
156
+ layoutInterfaceLinks.push([
157
+ `${JSON.stringify(layout[0])}: ${layout[1].interfaceName}`,
158
+ `${sub(camelToSnakeCase(layout[0]).toUpperCase().replace(" ", "_"))}: HOST.getLayout<${layout[1].interfaceName}>(${JSON.stringify(layout[0])})`
159
+ ])
160
+ }
161
+
162
+ stream.write(safeStringInjection`const HOST = new FMHost(${data.hostname}, ${data.timezoneOffset}, ${data.verify});`)
163
+ stream.write(`HOST.database<{layouts:{${
164
+ layoutInterfaceLinks.map(i => i[0]).join(",")
165
+ }}}>();`)
166
+
167
+ stream.write("\n\n")
168
+ stream.write(`const LAYOUTS = {${
169
+ layoutInterfaceLinks.map(i => i[1]).join(",")
170
+ }};`)
171
+
172
+ for (let layout of layoutInterfaces) {
173
+ stream.write("\n\n")
174
+ stream.write(layout[1].interface + ";")
175
+ }
176
+
177
+ // StructureKind
178
+ //
179
+ // const project = new Project({
180
+ // tsConfigFilePath: "../tsconfig.json"
181
+ // })
182
+ // project.resolveSourceFileDependencies()
183
+
184
+ await DATABASE.logout()
185
+ console.log("DONE! Wrote database types to ./types.ts")
186
+ }
@@ -8,7 +8,16 @@ export default class FMHost {
8
8
  hostname;
9
9
  timezoneOffset;
10
10
  verify;
11
- metadata;
11
+ metadata = {
12
+ productInfo: {
13
+ buildDate: new Date(),
14
+ name: "",
15
+ version: "",
16
+ dateFormat: "",
17
+ timeFormat: "",
18
+ timeStampFormat: ""
19
+ }
20
+ };
12
21
  constructor(_hostname, timezoneOffset = 0 - (new Date()).getTimezoneOffset(), verify = true) {
13
22
  if (!(/^https?:\/\//).test(_hostname))
14
23
  throw "hostname MUST begin with either http:// or https://";
@@ -27,7 +36,7 @@ export default class FMHost {
27
36
  });
28
37
  let data = await _fetch.json();
29
38
  // console.log(data.messages[0])
30
- if (data.messages[0].code === "0") {
39
+ if (data.messages[0].code === "0" && data.response) {
31
40
  return data.response.databases;
32
41
  }
33
42
  else {
@@ -46,7 +55,7 @@ export default class FMHost {
46
55
  });
47
56
  let data = await _fetch.json();
48
57
  // console.log(data.messages[0])
49
- if (data.messages[0].code === "0") {
58
+ if (data.messages[0].code === "0" && data.response) {
50
59
  this.metadata = data.response;
51
60
  return data.response;
52
61
  }
@@ -20,7 +20,16 @@ export default class FMHost implements HostBase {
20
20
  readonly hostname: string
21
21
  readonly timezoneOffset: number
22
22
  readonly verify: boolean
23
- metadata: FMHostMetadata
23
+ metadata: FMHostMetadata = {
24
+ productInfo: {
25
+ buildDate: new Date(),
26
+ name: "",
27
+ version: "",
28
+ dateFormat: "",
29
+ timeFormat: "",
30
+ timeStampFormat: ""
31
+ }
32
+ }
24
33
 
25
34
  constructor(_hostname: string, timezoneOffset = 0 - (new Date()).getTimezoneOffset(), verify = true) {
26
35
  if (!(/^https?:\/\//).test(_hostname)) throw "hostname MUST begin with either http:// or https://"
@@ -42,7 +51,7 @@ export default class FMHost implements HostBase {
42
51
  let data = await _fetch.json() as ApiResults<{databases: any}>
43
52
  // console.log(data.messages[0])
44
53
 
45
- if (data.messages[0].code === "0") {
54
+ if (data.messages[0].code === "0" && data.response) {
46
55
  return data.response.databases
47
56
  }
48
57
  else {
@@ -64,7 +73,7 @@ export default class FMHost implements HostBase {
64
73
  let data = await _fetch.json() as ApiResults<FMHostMetadata>
65
74
  // console.log(data.messages[0])
66
75
 
67
- if (data.messages[0].code === "0") {
76
+ if (data.messages[0].code === "0" && data.response) {
68
77
  this.metadata = data.response
69
78
  return data.response
70
79
  }
@@ -21,7 +21,8 @@ export declare class Database<T extends DatabaseStructure> extends EventEmitter
21
21
  login(): Promise<any>;
22
22
  get token(): any;
23
23
  get endpoint(): string;
24
- apiRequest<T = any>(url: string | Request, options?: any, autoRelogin?: boolean): Promise<ApiResults<T>>;
24
+ apiRequestRaw(url: string | Request, options?: any): Promise<Response>;
25
+ apiRequestJSON<T = any>(url: string | Request, options?: any, autoRelogin?: boolean): Promise<ApiResults<T>>;
25
26
  listLayouts(): Promise<Layout<LayoutInterface>[]>;
26
27
  getLayout<R extends keyof T["layouts"]>(name: R): Layout<T["layouts"][R]>;
27
28
  getLayout<R extends LayoutInterface>(name: string): Layout<R>;