@jd-data-limited/easy-fm 4.0.10 → 4.0.12
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/package.json +1 -1
- package/src/bin/cli.js +5 -7
- package/src/bin/generateTypes.js +127 -216
- package/src/bin/generateTypes.ts +10 -7
- package/src/connection/database.d.ts +1 -0
- package/src/connection/database.js +4 -0
- package/src/connection/database.ts +3 -0
- package/src/index.d.ts +3 -3
- package/src/index.js +2 -2
- package/src/index.ts +4 -4
- package/src/layouts/recordFieldsMap.d.ts +2 -2
- package/src/layouts/recordFieldsMap.ts +2 -2
- package/src/records/{fieldBase.d.ts → field.d.ts} +4 -3
- package/src/records/{fieldBase.js → field.js} +1 -7
- package/src/records/{fieldBase.ts → field.ts} +4 -11
- package/src/records/getOperations/recordGetOperation.d.ts +5 -1
- package/src/records/getOperations/recordGetOperation.js +22 -4
- package/src/records/getOperations/recordGetOperation.ts +26 -4
- package/src/records/layoutRecord.d.ts +3 -3
- package/src/records/layoutRecord.js +1 -1
- package/src/records/layoutRecord.ts +6 -6
- package/src/records/portalRecord.d.ts +2 -2
- package/src/records/portalRecord.ts +2 -2
- package/src/records/recordBase.d.ts +2 -2
- package/src/records/recordBase.js +5 -5
- package/src/records/recordBase.ts +7 -6
- package/src/types.d.ts +1 -0
- package/src/types.ts +2 -1
- package/src/utils/query.d.ts +4 -5
- package/src/utils/query.js +11 -4
- package/src/utils/query.ts +14 -7
- package/src/bin/example.xml +0 -0
package/package.json
CHANGED
package/src/bin/cli.js
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
2
|
/*
|
|
4
3
|
* Copyright (c) 2024. See LICENSE file for more information
|
|
5
4
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
var program = new commander_1.Command();
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import { generateTypesCLI } from "./generateTypes.js";
|
|
7
|
+
const program = new Command();
|
|
10
8
|
program
|
|
11
9
|
.name("easyfm")
|
|
12
10
|
.description("A NodeJS wrapper for the FileMaker Data CLI");
|
|
13
11
|
program
|
|
14
12
|
.command("generate-types")
|
|
15
13
|
.description("Automatically generate layout interfaces from your database")
|
|
16
|
-
.action(
|
|
17
|
-
|
|
14
|
+
.action(() => {
|
|
15
|
+
generateTypesCLI();
|
|
18
16
|
});
|
|
19
17
|
program.parse(process.argv);
|
package/src/bin/generateTypes.js
CHANGED
|
@@ -1,53 +1,10 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/*
|
|
3
2
|
* Copyright (c) 2024. See LICENSE file for more information
|
|
4
3
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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");
|
|
4
|
+
import { ApiFieldResultTypes } from "../models/apiResults.js";
|
|
5
|
+
import inquirer from 'inquirer';
|
|
6
|
+
import FMHost from "../connection/FMHost.js";
|
|
7
|
+
import * as fs from "fs";
|
|
51
8
|
function sub(str) {
|
|
52
9
|
return str
|
|
53
10
|
.replace(/[^a-zA-Z0-9_]/g, "") // Remove non-alphanumeric characters
|
|
@@ -55,42 +12,38 @@ function sub(str) {
|
|
|
55
12
|
}
|
|
56
13
|
function generateFieldType(field) {
|
|
57
14
|
switch (field.result) {
|
|
58
|
-
case
|
|
59
|
-
return "
|
|
60
|
-
case
|
|
61
|
-
return "
|
|
62
|
-
case
|
|
63
|
-
return "
|
|
64
|
-
case
|
|
65
|
-
return "
|
|
66
|
-
case
|
|
67
|
-
return "
|
|
68
|
-
case
|
|
69
|
-
return "
|
|
15
|
+
case ApiFieldResultTypes.TEXT:
|
|
16
|
+
return "Field<string>";
|
|
17
|
+
case ApiFieldResultTypes.CONTAINER:
|
|
18
|
+
return "Field<Container>";
|
|
19
|
+
case ApiFieldResultTypes.DATE:
|
|
20
|
+
return "Field<Date>";
|
|
21
|
+
case ApiFieldResultTypes.NUMBER:
|
|
22
|
+
return "Field<number>";
|
|
23
|
+
case ApiFieldResultTypes.TIME:
|
|
24
|
+
return "Field<Date>";
|
|
25
|
+
case ApiFieldResultTypes.TIMESTAMP:
|
|
26
|
+
return "Field<Date>";
|
|
70
27
|
}
|
|
71
28
|
}
|
|
72
29
|
function processFieldsChunk(fields) {
|
|
73
|
-
|
|
74
|
-
fields = fields.filter(
|
|
30
|
+
let existing_fields = [];
|
|
31
|
+
fields = fields.filter(field => {
|
|
75
32
|
if (existing_fields.includes(field.name))
|
|
76
33
|
return false;
|
|
77
34
|
existing_fields.push(field.name);
|
|
78
35
|
return true;
|
|
79
36
|
});
|
|
80
|
-
return fields.map(
|
|
37
|
+
return fields.map(field => {
|
|
81
38
|
return JSON.stringify(field.name) + ": " + generateFieldType(field);
|
|
82
39
|
});
|
|
83
40
|
}
|
|
84
41
|
function camelToSnakeCase(str) {
|
|
85
42
|
return str.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();
|
|
86
43
|
}
|
|
87
|
-
function safeStringInjection(str) {
|
|
88
|
-
|
|
89
|
-
for (
|
|
90
|
-
args[_i - 1] = arguments[_i];
|
|
91
|
-
}
|
|
92
|
-
var result = "";
|
|
93
|
-
for (var i = 0; i < str.length; i++) {
|
|
44
|
+
function safeStringInjection(str, ...args) {
|
|
45
|
+
let result = "";
|
|
46
|
+
for (let i = 0; i < str.length; i++) {
|
|
94
47
|
result += str[i];
|
|
95
48
|
if (i < args.length) {
|
|
96
49
|
result += JSON.stringify(args[i].toString());
|
|
@@ -98,153 +51,111 @@ function safeStringInjection(str) {
|
|
|
98
51
|
}
|
|
99
52
|
return result;
|
|
100
53
|
}
|
|
101
|
-
function generateTypesCLI() {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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*/];
|
|
54
|
+
export async function generateTypesCLI() {
|
|
55
|
+
process.stdout.write('\x1Bc');
|
|
56
|
+
console.log("For this tool to work, you are required to have access to an account on the target database.\n" +
|
|
57
|
+
"This account must have the fmrest extended privilege enabled.\n" +
|
|
58
|
+
"This tool can only export type interfaces for layouts that the given account has access to.");
|
|
59
|
+
console.log("\n");
|
|
60
|
+
const data = await inquirer.prompt([
|
|
61
|
+
{
|
|
62
|
+
type: "input",
|
|
63
|
+
name: "hostname",
|
|
64
|
+
message: "The IP address/domain of your FileMaker server (must start with either http:// or https://)",
|
|
65
|
+
validate(input) {
|
|
66
|
+
return input.startsWith("http://") || input.startsWith("https://") || "Please enter a valid IP address/domain starting with http:// or https://";
|
|
245
67
|
}
|
|
246
|
-
}
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
type: "number",
|
|
71
|
+
name: "timezoneOffset",
|
|
72
|
+
message: "Server timezone offset (minutes)",
|
|
73
|
+
default: 0 - (new Date()).getTimezoneOffset()
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
type: "confirm",
|
|
77
|
+
name: "verify",
|
|
78
|
+
default: true,
|
|
79
|
+
message: "Verify your server's identity",
|
|
80
|
+
when(previousAnswers) {
|
|
81
|
+
return previousAnswers.hostname.startsWith("https://");
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
type: "input",
|
|
86
|
+
name: "database",
|
|
87
|
+
message: "Database filename"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
type: "input",
|
|
91
|
+
name: "username",
|
|
92
|
+
message: "Database username"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
type: "password",
|
|
96
|
+
name: "password",
|
|
97
|
+
message: "Database password",
|
|
98
|
+
mask: "*"
|
|
99
|
+
}
|
|
100
|
+
]);
|
|
101
|
+
const HOST = new FMHost(data.hostname, data.timezoneOffset, data.verify);
|
|
102
|
+
const DATABASE = HOST.database({
|
|
103
|
+
database: data.database,
|
|
104
|
+
credentials: {
|
|
105
|
+
method: "filemaker",
|
|
106
|
+
username: data.username,
|
|
107
|
+
password: data.password
|
|
108
|
+
},
|
|
109
|
+
externalSources: []
|
|
247
110
|
});
|
|
111
|
+
await DATABASE.login();
|
|
112
|
+
// Create file write stream
|
|
113
|
+
const stream = fs.createWriteStream("./types.ts");
|
|
114
|
+
let layouts = await DATABASE.listLayouts();
|
|
115
|
+
stream.write(`import FMHost, {Container, FieldBase, LayoutInterface, Portal} from "@jd-data-limited/easy-fm";\n`);
|
|
116
|
+
stream.write("// " + layouts.length + " layouts found\n\n");
|
|
117
|
+
let layoutInterfaces = new Map();
|
|
118
|
+
for (let layout of layouts) {
|
|
119
|
+
let substituteName = sub(layout.name);
|
|
120
|
+
if (!substituteName)
|
|
121
|
+
continue;
|
|
122
|
+
console.log(substituteName);
|
|
123
|
+
let metadata = await layout.getLayoutMeta();
|
|
124
|
+
let fields = processFieldsChunk(metadata.fieldMetaData);
|
|
125
|
+
let portals = metadata.portalMetaData;
|
|
126
|
+
let portalsProcessed = Object.keys(portals).map(portalName => {
|
|
127
|
+
return `${JSON.stringify(portalName)}: Portal<{${processFieldsChunk(portals[portalName])}}>`;
|
|
128
|
+
});
|
|
129
|
+
let interfaceName = substituteName + "LayoutInterface";
|
|
130
|
+
layoutInterfaces.set(layout.name, {
|
|
131
|
+
interfaceName,
|
|
132
|
+
interface: `export interface ${interfaceName} extends LayoutInterface {
|
|
133
|
+
fields: {${fields.join(",\n ")}},
|
|
134
|
+
portals: {${portalsProcessed.join(",\n ")}}
|
|
135
|
+
}`
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
let layoutInterfaceLinks = [];
|
|
139
|
+
for (let layout of layoutInterfaces) {
|
|
140
|
+
layoutInterfaceLinks.push([
|
|
141
|
+
`${JSON.stringify(layout[0])}: ${layout[1].interfaceName}`,
|
|
142
|
+
`${sub(camelToSnakeCase(layout[0]).toUpperCase().replace(" ", "_"))}: HOST.getLayout<${layout[1].interfaceName}>(${JSON.stringify(layout[0])})`
|
|
143
|
+
]);
|
|
144
|
+
}
|
|
145
|
+
stream.write(safeStringInjection `const HOST = new FMHost(${data.hostname}, ${data.timezoneOffset}, ${data.verify});`);
|
|
146
|
+
stream.write(`HOST.database<{layouts:{${layoutInterfaceLinks.map(i => i[0]).join(",")}}}>();`);
|
|
147
|
+
stream.write("\n\n");
|
|
148
|
+
stream.write(`const LAYOUTS = {${layoutInterfaceLinks.map(i => i[1]).join(",")}};`);
|
|
149
|
+
for (let layout of layoutInterfaces) {
|
|
150
|
+
stream.write("\n\n");
|
|
151
|
+
stream.write(layout[1].interface + ";");
|
|
152
|
+
}
|
|
153
|
+
// StructureKind
|
|
154
|
+
//
|
|
155
|
+
// const project = new Project({
|
|
156
|
+
// tsConfigFilePath: "../tsconfig.json"
|
|
157
|
+
// })
|
|
158
|
+
// project.resolveSourceFileDependencies()
|
|
159
|
+
await DATABASE.logout();
|
|
160
|
+
console.log("DONE! Wrote database types to ./types.ts");
|
|
248
161
|
}
|
|
249
|
-
exports.generateTypesCLI = generateTypesCLI;
|
|
250
|
-
var templateObject_1;
|
package/src/bin/generateTypes.ts
CHANGED
|
@@ -16,17 +16,17 @@ function sub(str: string) {
|
|
|
16
16
|
function generateFieldType(field: ApiFieldMetadata) {
|
|
17
17
|
switch (field.result) {
|
|
18
18
|
case ApiFieldResultTypes.TEXT:
|
|
19
|
-
return "
|
|
19
|
+
return "Field<string>"
|
|
20
20
|
case ApiFieldResultTypes.CONTAINER:
|
|
21
|
-
return "
|
|
21
|
+
return "Field<Container>"
|
|
22
22
|
case ApiFieldResultTypes.DATE:
|
|
23
|
-
return "
|
|
23
|
+
return "Field<Date>"
|
|
24
24
|
case ApiFieldResultTypes.NUMBER:
|
|
25
|
-
return "
|
|
25
|
+
return "Field<number>"
|
|
26
26
|
case ApiFieldResultTypes.TIME:
|
|
27
|
-
return "
|
|
27
|
+
return "Field<Date>"
|
|
28
28
|
case ApiFieldResultTypes.TIMESTAMP:
|
|
29
|
-
return "
|
|
29
|
+
return "Field<Date>"
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -131,6 +131,9 @@ export async function generateTypesCLI() {
|
|
|
131
131
|
interface: string
|
|
132
132
|
}>()
|
|
133
133
|
for (let layout of layouts) {
|
|
134
|
+
let substituteName = sub(layout.name)
|
|
135
|
+
if (!substituteName) continue
|
|
136
|
+
console.log(substituteName)
|
|
134
137
|
let metadata = await layout.getLayoutMeta()
|
|
135
138
|
let fields = processFieldsChunk(metadata.fieldMetaData)
|
|
136
139
|
|
|
@@ -141,7 +144,7 @@ export async function generateTypesCLI() {
|
|
|
141
144
|
)}}>`
|
|
142
145
|
})
|
|
143
146
|
|
|
144
|
-
let interfaceName =
|
|
147
|
+
let interfaceName = substituteName + "LayoutInterface"
|
|
145
148
|
layoutInterfaces.set(layout.name, {
|
|
146
149
|
interfaceName,
|
|
147
150
|
interface: `export interface ${interfaceName} extends LayoutInterface {
|
|
@@ -15,6 +15,7 @@ export declare class Database<T extends DatabaseStructure> extends EventEmitter
|
|
|
15
15
|
private connection_details;
|
|
16
16
|
private cookies;
|
|
17
17
|
readonly name: string;
|
|
18
|
+
readonly debug: boolean;
|
|
18
19
|
constructor(host: HostBase, conn: databaseOptionsWithExternalSources);
|
|
19
20
|
private generateExternalSourceLogin;
|
|
20
21
|
logout(): Promise<void>;
|
|
@@ -12,11 +12,13 @@ export class Database extends EventEmitter {
|
|
|
12
12
|
connection_details;
|
|
13
13
|
cookies = {};
|
|
14
14
|
name;
|
|
15
|
+
debug;
|
|
15
16
|
constructor(host, conn) {
|
|
16
17
|
super();
|
|
17
18
|
this.host = host;
|
|
18
19
|
this.name = conn.database;
|
|
19
20
|
this.connection_details = conn;
|
|
21
|
+
this.debug = !!conn.debug;
|
|
20
22
|
}
|
|
21
23
|
generateExternalSourceLogin(data) {
|
|
22
24
|
if (data.credentials.method === "filemaker") {
|
|
@@ -86,6 +88,8 @@ export class Database extends EventEmitter {
|
|
|
86
88
|
return `${this.host.hostname}/fmi/data/v2/databases/${this.name}`;
|
|
87
89
|
}
|
|
88
90
|
async apiRequestRaw(url, options = {}, autoRelogin = true) {
|
|
91
|
+
if (this.debug)
|
|
92
|
+
console.log(`EASYFM DEBUG: ${options.method?.toString().toUpperCase() || "GET"} ${url instanceof Request ? url.url : url}`);
|
|
89
93
|
if (!this.token)
|
|
90
94
|
await this.login(true);
|
|
91
95
|
if (!options.headers)
|
|
@@ -29,12 +29,14 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
|
|
|
29
29
|
private connection_details: databaseOptionsWithExternalSources
|
|
30
30
|
private cookies: { [key: string]: string } = {}
|
|
31
31
|
readonly name: string;
|
|
32
|
+
readonly debug: boolean
|
|
32
33
|
|
|
33
34
|
constructor(host: HostBase, conn: databaseOptionsWithExternalSources) {
|
|
34
35
|
super()
|
|
35
36
|
this.host = host
|
|
36
37
|
this.name = conn.database
|
|
37
38
|
this.connection_details = conn
|
|
39
|
+
this.debug = !!conn.debug
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
private generateExternalSourceLogin(data: databaseOptionsBase) {
|
|
@@ -111,6 +113,7 @@ export class Database<T extends DatabaseStructure> extends EventEmitter implemen
|
|
|
111
113
|
}
|
|
112
114
|
|
|
113
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}`)
|
|
114
117
|
if (!this.token) await this.login(true)
|
|
115
118
|
|
|
116
119
|
if (!options.headers) options.headers = {}
|
package/src/index.d.ts
CHANGED
|
@@ -9,10 +9,10 @@ import { LayoutRecord } from "./records/layoutRecord.js";
|
|
|
9
9
|
import { LayoutInterface, PortalInterface } from "./layouts/layoutInterface.js";
|
|
10
10
|
import { PortalRecord } from "./records/portalRecord.js";
|
|
11
11
|
import { Portal } from "./records/portal.js";
|
|
12
|
-
import {
|
|
13
|
-
import { FindRequest, RecordGetOperation } from "./records/getOperations/recordGetOperation.js";
|
|
12
|
+
import { FindRequestRaw, RecordGetOperation } from "./records/getOperations/recordGetOperation.js";
|
|
14
13
|
import { LayoutRecordManager } from "./layouts/layoutRecordManager.js";
|
|
15
14
|
import { RecordFieldsMap } from "./layouts/recordFieldsMap.js";
|
|
16
15
|
import { query, queryEscape } from "./utils/query.js";
|
|
16
|
+
import { Container, Field } from "./records/field.js";
|
|
17
17
|
export default FMHost;
|
|
18
|
-
export { FMError, TYPES, Database, Layout, RecordBase, LayoutRecord, LayoutInterface, RecordFieldsMap, PortalInterface, PortalRecord, Portal,
|
|
18
|
+
export { FMError, TYPES, Database, Layout, RecordBase, LayoutRecord, LayoutInterface, RecordFieldsMap, PortalInterface, PortalRecord, Portal, Field, RecordGetOperation, LayoutRecordManager, Container, PickPortals, FindRequestRaw, query, queryEscape };
|
package/src/index.js
CHANGED
|
@@ -10,9 +10,9 @@ import { RecordBase } from "./records/recordBase.js";
|
|
|
10
10
|
import { LayoutRecord } from "./records/layoutRecord.js";
|
|
11
11
|
import { PortalRecord } from "./records/portalRecord.js";
|
|
12
12
|
import { Portal } from "./records/portal.js";
|
|
13
|
-
import { FieldBase } from "./records/fieldBase.js";
|
|
14
13
|
import { RecordGetOperation } from "./records/getOperations/recordGetOperation.js";
|
|
15
14
|
import { LayoutRecordManager } from "./layouts/layoutRecordManager.js";
|
|
16
15
|
import { query, queryEscape } from "./utils/query.js";
|
|
16
|
+
import { Field } from "./records/field.js";
|
|
17
17
|
export default FMHost;
|
|
18
|
-
export { FMError, TYPES, Database, Layout, RecordBase, LayoutRecord, PortalRecord, Portal,
|
|
18
|
+
export { FMError, TYPES, Database, Layout, RecordBase, LayoutRecord, PortalRecord, Portal, Field, RecordGetOperation, LayoutRecordManager, query, queryEscape };
|
package/src/index.ts
CHANGED
|
@@ -13,11 +13,11 @@ import {LayoutRecord} from "./records/layoutRecord.js";
|
|
|
13
13
|
import {LayoutInterface, PortalInterface} from "./layouts/layoutInterface.js";
|
|
14
14
|
import {PortalRecord} from "./records/portalRecord.js";
|
|
15
15
|
import {Portal} from "./records/portal.js";
|
|
16
|
-
import {
|
|
17
|
-
import {FindRequest, RecordGetOperation} from "./records/getOperations/recordGetOperation.js";
|
|
16
|
+
import {FindRequestRaw, RecordGetOperation} from "./records/getOperations/recordGetOperation.js";
|
|
18
17
|
import {LayoutRecordManager} from "./layouts/layoutRecordManager.js";
|
|
19
18
|
import {RecordFieldsMap} from "./layouts/recordFieldsMap.js";
|
|
20
19
|
import {query, queryEscape} from "./utils/query.js";
|
|
20
|
+
import {Container, Field} from "./records/field.js";
|
|
21
21
|
|
|
22
22
|
export default FMHost
|
|
23
23
|
export {
|
|
@@ -32,12 +32,12 @@ export {
|
|
|
32
32
|
PortalInterface,
|
|
33
33
|
PortalRecord,
|
|
34
34
|
Portal,
|
|
35
|
-
|
|
35
|
+
Field,
|
|
36
36
|
RecordGetOperation,
|
|
37
37
|
LayoutRecordManager,
|
|
38
38
|
Container,
|
|
39
39
|
PickPortals,
|
|
40
|
-
|
|
40
|
+
FindRequestRaw,
|
|
41
41
|
query,
|
|
42
42
|
queryEscape
|
|
43
43
|
}
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Copyright (c) 2023-2024. See LICENSE file for more information
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {Field, FieldValue} from "../records/field.js";
|
|
6
6
|
|
|
7
7
|
export interface RecordFieldsMap {
|
|
8
|
-
[fieldName: string]:
|
|
8
|
+
[fieldName: string]: Field<FieldValue>
|
|
9
9
|
}
|
|
@@ -4,20 +4,21 @@ import * as http from "http";
|
|
|
4
4
|
import { ContainerBufferResult, DownloadModes, RecordTypes } from "../types.js";
|
|
5
5
|
import { ApiFieldMetadata } from "../models/apiResults.js";
|
|
6
6
|
import { LayoutBase } from "../layouts/layoutBase.js";
|
|
7
|
-
|
|
7
|
+
import { Moment } from "moment";
|
|
8
|
+
export type FieldValue = string | number | Moment | Container;
|
|
8
9
|
export type Container = null;
|
|
9
10
|
export type Parentable = {
|
|
10
11
|
layout: LayoutBase;
|
|
11
12
|
type: RecordTypes;
|
|
12
13
|
endpoint: string;
|
|
13
14
|
};
|
|
14
|
-
export declare class
|
|
15
|
+
export declare class Field<T extends FieldValue> {
|
|
15
16
|
parent: Parentable;
|
|
16
17
|
id: string;
|
|
17
18
|
protected _value: T;
|
|
18
19
|
edited: boolean;
|
|
19
20
|
constructor(record: Parentable, id: string, contents: T);
|
|
20
|
-
set(content:
|
|
21
|
+
set(content: T): void;
|
|
21
22
|
get metadata(): ApiFieldMetadata;
|
|
22
23
|
get value(): T;
|
|
23
24
|
set value(value: T);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { DownloadModes, RecordTypes } from "../types.js";
|
|
5
5
|
import { ApiFieldDisplayTypes, ApiFieldResultTypes, ApiFieldTypes } from "../models/apiResults.js";
|
|
6
|
-
export class
|
|
6
|
+
export class Field {
|
|
7
7
|
parent;
|
|
8
8
|
id;
|
|
9
9
|
_value;
|
|
@@ -17,12 +17,6 @@ export class FieldBase {
|
|
|
17
17
|
set(content) {
|
|
18
18
|
if (this.metadata.result === "container")
|
|
19
19
|
throw "Cannot set container value using set(). Use upload() instead.";
|
|
20
|
-
if ((this.metadata.result === "timeStamp" ||
|
|
21
|
-
this.metadata.result === "date" ||
|
|
22
|
-
this.metadata.result === "time")
|
|
23
|
-
&& !(content instanceof Date) && !!content) {
|
|
24
|
-
throw "Value was not an instance of Date: " + content;
|
|
25
|
-
}
|
|
26
20
|
// @ts-ignore
|
|
27
21
|
if (!content)
|
|
28
22
|
this._value = "";
|
|
@@ -6,12 +6,13 @@ import * as http from "http";
|
|
|
6
6
|
import {ContainerBufferResult, DownloadModes, FieldMetaData, RecordTypes} from "../types.js";
|
|
7
7
|
import {ApiFieldDisplayTypes, ApiFieldMetadata, ApiFieldResultTypes, ApiFieldTypes} from "../models/apiResults.js";
|
|
8
8
|
import {LayoutBase} from "../layouts/layoutBase.js";
|
|
9
|
+
import {Moment} from "moment";
|
|
9
10
|
|
|
10
|
-
export type FieldValue = string | number |
|
|
11
|
+
export type FieldValue = string | number | Moment | Container
|
|
11
12
|
export type Container = null
|
|
12
13
|
|
|
13
14
|
export type Parentable = {layout: LayoutBase, type: RecordTypes, endpoint: string}
|
|
14
|
-
export class
|
|
15
|
+
export class Field<T extends FieldValue> {
|
|
15
16
|
parent: Parentable;
|
|
16
17
|
id: string;
|
|
17
18
|
protected _value: T;
|
|
@@ -24,16 +25,8 @@ export class FieldBase<T extends FieldValue> {
|
|
|
24
25
|
this.edited = false
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
set(content:
|
|
28
|
+
set(content: T) {
|
|
28
29
|
if (this.metadata.result === "container") throw "Cannot set container value using set(). Use upload() instead."
|
|
29
|
-
if (
|
|
30
|
-
(this.metadata.result === "timeStamp" ||
|
|
31
|
-
this.metadata.result === "date" ||
|
|
32
|
-
this.metadata.result === "time")
|
|
33
|
-
&& !(content instanceof Date) && !!content
|
|
34
|
-
) {
|
|
35
|
-
throw "Value was not an instance of Date: " + content
|
|
36
|
-
}
|
|
37
30
|
// @ts-ignore
|
|
38
31
|
if (!content) this._value = ""
|
|
39
32
|
// @ts-ignore
|
|
@@ -4,6 +4,9 @@ import { LayoutBase } from "../../layouts/layoutBase.js";
|
|
|
4
4
|
import { LayoutRecord } from "../layoutRecord.js";
|
|
5
5
|
import { Query } from "../../utils/query.js";
|
|
6
6
|
export type SortOrder = "ascend" | "descend";
|
|
7
|
+
export type FindRequestRaw = {
|
|
8
|
+
[key: string]: string;
|
|
9
|
+
};
|
|
7
10
|
export type FindRequest = {
|
|
8
11
|
[key: string]: Query;
|
|
9
12
|
};
|
|
@@ -32,7 +35,7 @@ export declare class RecordGetOperation<T extends LayoutInterface, OPTIONS exten
|
|
|
32
35
|
protected portals: Partial<PortalData<T>>;
|
|
33
36
|
protected offset: number;
|
|
34
37
|
protected queries: {
|
|
35
|
-
req:
|
|
38
|
+
req: FindRequestRaw;
|
|
36
39
|
omit: boolean;
|
|
37
40
|
}[];
|
|
38
41
|
constructor(layout: LayoutBase, options: OPTIONS);
|
|
@@ -44,6 +47,7 @@ export declare class RecordGetOperation<T extends LayoutInterface, OPTIONS exten
|
|
|
44
47
|
protected generateParamsURL(offset: number, limit: number): URLSearchParams;
|
|
45
48
|
scripts(scripts: ScriptRequestData): this;
|
|
46
49
|
sort(fieldName: string, sortOrder: SortOrder): this;
|
|
50
|
+
private parseFindRequest;
|
|
47
51
|
addRequest(query: FindRequest, omit?: boolean): this;
|
|
48
52
|
fetch(): Promise<LayoutRecord<PickPortals<T, keyof OPTIONS["portals"]>>[]>;
|
|
49
53
|
private performFind;
|
|
@@ -23,11 +23,11 @@ export class RecordGetOperation {
|
|
|
23
23
|
return this.queries.length !== 0;
|
|
24
24
|
}
|
|
25
25
|
formatQueries() {
|
|
26
|
-
|
|
26
|
+
let test = this.queries.map(query => {
|
|
27
27
|
let out = {};
|
|
28
28
|
for (let key of Object.keys(query.req)) {
|
|
29
|
-
if (query.req[key]
|
|
30
|
-
out[key] = query.req[key]
|
|
29
|
+
if (query.req[key])
|
|
30
|
+
out[key] = query.req[key];
|
|
31
31
|
else {
|
|
32
32
|
out[key] = query.req[key];
|
|
33
33
|
}
|
|
@@ -36,11 +36,13 @@ export class RecordGetOperation {
|
|
|
36
36
|
out.omit = "true";
|
|
37
37
|
return out;
|
|
38
38
|
});
|
|
39
|
+
return test;
|
|
39
40
|
}
|
|
40
41
|
generateParamsBody(offset, limit) {
|
|
41
42
|
const params = {
|
|
42
43
|
limit: limit.toString(),
|
|
43
44
|
offset: offset.toString(),
|
|
45
|
+
dateformats: 2 // Ensure dates are received in ISO8601 format
|
|
44
46
|
};
|
|
45
47
|
if (this.sortData.length !== 0)
|
|
46
48
|
params.sort = this.sortData;
|
|
@@ -70,6 +72,7 @@ export class RecordGetOperation {
|
|
|
70
72
|
const params = new URLSearchParams({
|
|
71
73
|
_limit: limit.toString(),
|
|
72
74
|
_offset: offset.toString(),
|
|
75
|
+
dateformats: "2" // Ensure dates are received in ISO8601 format
|
|
73
76
|
});
|
|
74
77
|
if (this.sortData.length !== 0)
|
|
75
78
|
params.set("_sort", JSON.stringify(this.sortData));
|
|
@@ -101,8 +104,23 @@ export class RecordGetOperation {
|
|
|
101
104
|
this.sortData.push({ fieldName, sortOrder });
|
|
102
105
|
return this;
|
|
103
106
|
}
|
|
107
|
+
parseFindRequest(query) {
|
|
108
|
+
let out = {};
|
|
109
|
+
for (let key of Object.keys(query)) {
|
|
110
|
+
out[key] = query[key][FindRequestSymbol].map(item => {
|
|
111
|
+
if (typeof item === "string")
|
|
112
|
+
return item;
|
|
113
|
+
// Re-write date into correct format
|
|
114
|
+
return item
|
|
115
|
+
.clone()
|
|
116
|
+
.utcOffset(this.layout.database.host.timezoneOffset)
|
|
117
|
+
.format("MM/DD/YYYY HH:mm:ss");
|
|
118
|
+
}).join("");
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
104
122
|
addRequest(query, omit = false) {
|
|
105
|
-
this.queries.push({ req: query, omit });
|
|
123
|
+
this.queries.push({ req: this.parseFindRequest(query), omit });
|
|
106
124
|
return this;
|
|
107
125
|
}
|
|
108
126
|
fetch() {
|
|
@@ -11,6 +11,9 @@ import {FMError} from "../../FMError.js";
|
|
|
11
11
|
import {FindRequestSymbol, Query} from "../../utils/query.js";
|
|
12
12
|
|
|
13
13
|
export type SortOrder = "ascend" | "descend"
|
|
14
|
+
export type FindRequestRaw = {
|
|
15
|
+
[key: string]: string
|
|
16
|
+
}
|
|
14
17
|
export type FindRequest = {
|
|
15
18
|
[key: string]: Query
|
|
16
19
|
}
|
|
@@ -38,7 +41,7 @@ export class RecordGetOperation<T extends LayoutInterface, OPTIONS extends GetOp
|
|
|
38
41
|
protected sortData: { fieldName: string, sortOrder: SortOrder }[] = []
|
|
39
42
|
protected portals: Partial<PortalData<T>>
|
|
40
43
|
protected offset: number = 1
|
|
41
|
-
protected queries: {req:
|
|
44
|
+
protected queries: {req: FindRequestRaw, omit: boolean}[] = []
|
|
42
45
|
|
|
43
46
|
constructor(layout: LayoutBase, options: OPTIONS) {
|
|
44
47
|
this.layout = layout
|
|
@@ -53,21 +56,23 @@ export class RecordGetOperation<T extends LayoutInterface, OPTIONS extends GetOp
|
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
private formatQueries() {
|
|
56
|
-
|
|
59
|
+
let test = this.queries.map(query => {
|
|
57
60
|
let out: any = {}
|
|
58
61
|
for (let key of Object.keys(query.req)) {
|
|
59
|
-
if (query.req[key]
|
|
62
|
+
if (query.req[key]) out[key] = query.req[key]
|
|
60
63
|
else {out[key] = query.req[key]}
|
|
61
64
|
}
|
|
62
65
|
if (query.omit) out.omit = "true"
|
|
63
66
|
return out
|
|
64
67
|
})
|
|
68
|
+
return test
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
protected generateParamsBody(offset: number, limit: number) {
|
|
68
72
|
const params: {[key: string]: any} = {
|
|
69
73
|
limit: limit.toString(),
|
|
70
74
|
offset: offset.toString(),
|
|
75
|
+
dateformats: 2 // Ensure dates are received in ISO8601 format
|
|
71
76
|
}
|
|
72
77
|
if (this.sortData.length !== 0) params.sort = this.sortData
|
|
73
78
|
|
|
@@ -96,6 +101,7 @@ export class RecordGetOperation<T extends LayoutInterface, OPTIONS extends GetOp
|
|
|
96
101
|
const params = new URLSearchParams({
|
|
97
102
|
_limit: limit.toString(),
|
|
98
103
|
_offset: offset.toString(),
|
|
104
|
+
dateformats: "2" // Ensure dates are received in ISO8601 format
|
|
99
105
|
})
|
|
100
106
|
if (this.sortData.length !== 0) params.set("_sort", JSON.stringify(this.sortData))
|
|
101
107
|
|
|
@@ -128,8 +134,24 @@ export class RecordGetOperation<T extends LayoutInterface, OPTIONS extends GetOp
|
|
|
128
134
|
return this
|
|
129
135
|
}
|
|
130
136
|
|
|
137
|
+
private parseFindRequest<I extends FindRequest>(query: I): {[key in keyof I]: string} {
|
|
138
|
+
let out: any = {}
|
|
139
|
+
for (let key of Object.keys(query)) {
|
|
140
|
+
out[key] = query[key][FindRequestSymbol].map(item => {
|
|
141
|
+
if (typeof item === "string") return item
|
|
142
|
+
|
|
143
|
+
// Re-write date into correct format
|
|
144
|
+
return item
|
|
145
|
+
.clone()
|
|
146
|
+
.utcOffset(this.layout.database.host.timezoneOffset)
|
|
147
|
+
.format("MM/DD/YYYY HH:mm:ss")
|
|
148
|
+
}).join("")
|
|
149
|
+
}
|
|
150
|
+
return out
|
|
151
|
+
}
|
|
152
|
+
|
|
131
153
|
addRequest(query: FindRequest, omit = false) {
|
|
132
|
-
this.queries.push({req: query, omit})
|
|
154
|
+
this.queries.push({req: this.parseFindRequest(query), omit})
|
|
133
155
|
return this
|
|
134
156
|
}
|
|
135
157
|
|
|
@@ -6,7 +6,7 @@ import { LayoutInterface } from "../layouts/layoutInterface.js";
|
|
|
6
6
|
import { LayoutRecordBase } from "./layoutRecordBase.js";
|
|
7
7
|
import { ApiPortalData, ApiRowDataDef } from "../models/apiResults.js";
|
|
8
8
|
import { LayoutBase } from "../layouts/layoutBase.js";
|
|
9
|
-
import {
|
|
9
|
+
import { Field, FieldValue } from "./field.js";
|
|
10
10
|
export declare class LayoutRecord<LAYOUT extends LayoutInterface> extends RecordBase<LAYOUT["fields"]> implements LayoutRecordBase {
|
|
11
11
|
portals: LAYOUT["portals"];
|
|
12
12
|
private readonly portalsToInclude;
|
|
@@ -17,6 +17,6 @@ export declare class LayoutRecord<LAYOUT extends LayoutInterface> extends Record
|
|
|
17
17
|
get(): Promise<this>;
|
|
18
18
|
duplicate(): Promise<LayoutRecord<LAYOUT>>;
|
|
19
19
|
delete(): Promise<void>;
|
|
20
|
-
fieldsToObject(filter?: (a:
|
|
21
|
-
toObject(filter?: (a:
|
|
20
|
+
fieldsToObject(filter?: (a: Field<FieldValue>) => boolean): Omit<ApiRowDataDef, "portalData">;
|
|
21
|
+
toObject(filter?: (a: Field<FieldValue>) => any, portalFilter?: (a: Portal<any>) => any, portalRowFilter?: (a: PortalRecord<never>) => any, portalFieldFilter?: (a: Field<FieldValue>) => any): ApiRowDataDef;
|
|
22
22
|
}
|
|
@@ -186,7 +186,7 @@ export class LayoutRecord extends RecordBase {
|
|
|
186
186
|
toObject(filter = (a) => a.edited, portalFilter = (a) => a.records.find(record => record.edited), portalRowFilter = (a) => a.edited, portalFieldFilter = (a) => a.edited) {
|
|
187
187
|
let obj = {
|
|
188
188
|
...this.fieldsToObject(filter),
|
|
189
|
-
portalData: {}
|
|
189
|
+
portalData: {},
|
|
190
190
|
};
|
|
191
191
|
// Check if there's been any edited portal information
|
|
192
192
|
let portals = this.portalsArray.filter(a => portalFilter(a));
|
|
@@ -11,8 +11,8 @@ import {FMError} from "../FMError.js";
|
|
|
11
11
|
import {LayoutRecordBase} from "./layoutRecordBase.js";
|
|
12
12
|
import {ApiFieldData, ApiPortalData, ApiRecordResponseObj, ApiRowDataDef} from "../models/apiResults.js";
|
|
13
13
|
import {LayoutBase} from "../layouts/layoutBase.js"
|
|
14
|
-
import {FieldBase, FieldValue} from "./fieldBase.js";
|
|
15
14
|
import * as moment from "moment/moment.js";
|
|
15
|
+
import {Field, FieldValue} from "./field.js";
|
|
16
16
|
|
|
17
17
|
export class LayoutRecord<LAYOUT extends LayoutInterface> extends RecordBase<LAYOUT["fields"]> implements LayoutRecordBase {
|
|
18
18
|
// @ts-ignore
|
|
@@ -182,9 +182,9 @@ export class LayoutRecord<LAYOUT extends LayoutInterface> extends RecordBase<LAY
|
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
fieldsToObject(filter = (a:
|
|
185
|
+
fieldsToObject(filter = (a: Field<FieldValue>) => a.edited): Omit<ApiRowDataDef, "portalData"> {
|
|
186
186
|
let fields_processed: ApiFieldData = {}
|
|
187
|
-
let field:
|
|
187
|
+
let field: Field<FieldValue>
|
|
188
188
|
for (field of this.fieldsArray.filter(field => filter(field))) {
|
|
189
189
|
let value = field.value as string | number | Date
|
|
190
190
|
if (value instanceof Date) {
|
|
@@ -216,14 +216,14 @@ export class LayoutRecord<LAYOUT extends LayoutInterface> extends RecordBase<LAY
|
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
toObject(
|
|
219
|
-
filter: (a:
|
|
219
|
+
filter: (a: Field<FieldValue>) => any = (a) => a.edited,
|
|
220
220
|
portalFilter: (a: Portal<any>) => any = (a) => a.records.find(record => record.edited),
|
|
221
221
|
portalRowFilter: (a: PortalRecord<never>) => any = (a) => a.edited,
|
|
222
|
-
portalFieldFilter: (a:
|
|
222
|
+
portalFieldFilter: (a: Field<FieldValue>) => any = (a) => a.edited
|
|
223
223
|
) {
|
|
224
224
|
let obj: ApiRowDataDef = {
|
|
225
225
|
...this.fieldsToObject(filter),
|
|
226
|
-
portalData: {}
|
|
226
|
+
portalData: {},
|
|
227
227
|
};
|
|
228
228
|
|
|
229
229
|
// Check if there's been any edited portal information
|
|
@@ -2,14 +2,14 @@ import { RecordBase } from "./recordBase.js";
|
|
|
2
2
|
import { extraBodyOptions, RecordTypes } from "../types.js";
|
|
3
3
|
import { RecordFieldsMap } from "../layouts/recordFieldsMap.js";
|
|
4
4
|
import { PortalBase } from "./portalBase.js";
|
|
5
|
-
import {
|
|
5
|
+
import { Field, FieldValue } from "./field.js";
|
|
6
6
|
export declare class PortalRecord<T extends RecordFieldsMap> extends RecordBase<T> {
|
|
7
7
|
readonly portal: PortalBase<T>;
|
|
8
8
|
readonly type = RecordTypes.PORTAL;
|
|
9
9
|
constructor(record: RecordBase<any>, portal: PortalBase<any>, recordId: number, modId?: number, fieldData?: {});
|
|
10
10
|
_onSave(): void;
|
|
11
11
|
commit(extraBody?: extraBodyOptions): Promise<import("./layoutRecord.js").LayoutRecord<import("../index.js").LayoutInterface>>;
|
|
12
|
-
toObject(fieldFilter: (a:
|
|
12
|
+
toObject(fieldFilter: (a: Field<FieldValue>) => boolean): {
|
|
13
13
|
modId?: string;
|
|
14
14
|
recordId?: string;
|
|
15
15
|
} & {
|
|
@@ -7,7 +7,7 @@ import {extraBodyOptions, RecordTypes} from "../types.js";
|
|
|
7
7
|
|
|
8
8
|
import {RecordFieldsMap} from "../layouts/recordFieldsMap.js";
|
|
9
9
|
import {PortalBase} from "./portalBase.js";
|
|
10
|
-
import {
|
|
10
|
+
import {Field, FieldValue} from "./field.js";
|
|
11
11
|
|
|
12
12
|
export class PortalRecord<T extends RecordFieldsMap> extends RecordBase<T> {
|
|
13
13
|
readonly portal: PortalBase<T>;
|
|
@@ -27,7 +27,7 @@ export class PortalRecord<T extends RecordFieldsMap> extends RecordBase<T> {
|
|
|
27
27
|
return this.portal.record.commit(extraBody)
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
toObject(fieldFilter: (a:
|
|
30
|
+
toObject(fieldFilter: (a: Field<FieldValue>) => boolean): {
|
|
31
31
|
modId?: string,
|
|
32
32
|
recordId?: string,
|
|
33
33
|
} & {[key: string]: string} {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
|
2
2
|
import { EventEmitter } from "events";
|
|
3
|
-
import { FieldBase, FieldValue } from "./fieldBase.js";
|
|
4
3
|
import { RecordTypes } from "../types.js";
|
|
5
4
|
import { RecordFieldsMap } from "../layouts/recordFieldsMap.js";
|
|
6
5
|
import { LayoutBase } from "../layouts/layoutBase.js";
|
|
7
6
|
import { ApiFieldData } from "../models/apiResults.js";
|
|
7
|
+
import { Field, FieldValue } from "./field.js";
|
|
8
8
|
export declare abstract class RecordBase<T extends RecordFieldsMap> extends EventEmitter {
|
|
9
9
|
readonly layout: LayoutBase;
|
|
10
10
|
readonly type: RecordTypes;
|
|
@@ -15,7 +15,7 @@ export declare abstract class RecordBase<T extends RecordFieldsMap> extends Even
|
|
|
15
15
|
protected constructor(layout: LayoutBase, recordId: number, modId: number | undefined, fieldData: ApiFieldData);
|
|
16
16
|
get endpoint(): string;
|
|
17
17
|
get edited(): boolean;
|
|
18
|
-
get fieldsArray():
|
|
18
|
+
get fieldsArray(): Field<FieldValue>[];
|
|
19
19
|
protected processFieldData(fieldData: ApiFieldData): T;
|
|
20
20
|
_onSave(): void;
|
|
21
21
|
}
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { EventEmitter } from "events";
|
|
5
5
|
import * as moment from "moment";
|
|
6
|
-
import { FieldBase } from "./fieldBase.js";
|
|
7
6
|
import { RecordTypes } from "../types.js";
|
|
7
|
+
import { Field } from "./field.js";
|
|
8
8
|
export class RecordBase extends EventEmitter {
|
|
9
9
|
layout;
|
|
10
10
|
type = RecordTypes.UNKNOWN;
|
|
@@ -31,14 +31,14 @@ export class RecordBase extends EventEmitter {
|
|
|
31
31
|
processFieldData(fieldData) {
|
|
32
32
|
let fields = {};
|
|
33
33
|
for (let key of Object.keys(fieldData)) {
|
|
34
|
-
let _field = new
|
|
34
|
+
let _field = new Field(this, key, fieldData[key]);
|
|
35
35
|
if (!!fieldData[key]) {
|
|
36
36
|
if (_field.metadata.result === "timeStamp") {
|
|
37
37
|
// @ts-ignore
|
|
38
38
|
let date = moment.default(fieldData[key], this.layout.database.host.metadata.productInfo.timeStampFormat.replace("dd", "DD"))
|
|
39
39
|
.utcOffset(this.layout.database.host.timezoneOffset, true)
|
|
40
40
|
.local();
|
|
41
|
-
_field.set(date
|
|
41
|
+
_field.set(date);
|
|
42
42
|
_field.edited = false;
|
|
43
43
|
}
|
|
44
44
|
else if (_field.metadata.result === "time") {
|
|
@@ -46,7 +46,7 @@ export class RecordBase extends EventEmitter {
|
|
|
46
46
|
let date = moment.default(fieldData[key], this.layout.database.host.metadata.productInfo.timeFormat.replace("dd", "DD"))
|
|
47
47
|
.utcOffset(this.layout.database.host.timezoneOffset, true)
|
|
48
48
|
.local();
|
|
49
|
-
_field.set(date
|
|
49
|
+
_field.set(date);
|
|
50
50
|
_field.edited = false;
|
|
51
51
|
}
|
|
52
52
|
else if (_field.metadata.result === "date") {
|
|
@@ -54,7 +54,7 @@ export class RecordBase extends EventEmitter {
|
|
|
54
54
|
let date = moment.default(fieldData[key], this.layout.database.host.metadata.productInfo.dateFormat.replace("dd", "DD"))
|
|
55
55
|
.utcOffset(this.layout.database.host.timezoneOffset, true)
|
|
56
56
|
.local();
|
|
57
|
-
_field.set(date
|
|
57
|
+
_field.set(date);
|
|
58
58
|
_field.edited = false;
|
|
59
59
|
}
|
|
60
60
|
}
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
import {EventEmitter} from "events";
|
|
6
6
|
import * as moment from "moment";
|
|
7
|
-
import {
|
|
7
|
+
import {Moment} from "moment";
|
|
8
8
|
import {RecordTypes} from "../types.js";
|
|
9
9
|
import {RecordFieldsMap} from "../layouts/recordFieldsMap.js";
|
|
10
10
|
import {LayoutBase} from "../layouts/layoutBase.js";
|
|
11
11
|
import {ApiFieldData} from "../models/apiResults.js";
|
|
12
|
+
import {Field, FieldValue} from "./field.js";
|
|
12
13
|
|
|
13
14
|
export abstract class RecordBase<T extends RecordFieldsMap> extends EventEmitter {
|
|
14
15
|
readonly layout: LayoutBase;
|
|
@@ -34,7 +35,7 @@ export abstract class RecordBase<T extends RecordFieldsMap> extends EventEmitter
|
|
|
34
35
|
return !!this.fieldsArray.find(i => i.edited)
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
get fieldsArray():
|
|
38
|
+
get fieldsArray(): Field<FieldValue>[] {
|
|
38
39
|
return Object.values(this.fields)
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -42,14 +43,14 @@ export abstract class RecordBase<T extends RecordFieldsMap> extends EventEmitter
|
|
|
42
43
|
let fields: RecordFieldsMap = {}
|
|
43
44
|
|
|
44
45
|
for (let key of Object.keys(fieldData)) {
|
|
45
|
-
let _field = new
|
|
46
|
+
let _field = new Field<number | string | Moment>(this, key, fieldData[key])
|
|
46
47
|
if (!!fieldData[key]) {
|
|
47
48
|
if (_field.metadata.result === "timeStamp") {
|
|
48
49
|
// @ts-ignore
|
|
49
50
|
let date = moment.default(fieldData[key], this.layout.database.host.metadata.productInfo.timeStampFormat.replace("dd", "DD"))
|
|
50
51
|
.utcOffset(this.layout.database.host.timezoneOffset, true)
|
|
51
52
|
.local()
|
|
52
|
-
_field.set(date
|
|
53
|
+
_field.set(date)
|
|
53
54
|
_field.edited = false
|
|
54
55
|
|
|
55
56
|
}
|
|
@@ -58,7 +59,7 @@ export abstract class RecordBase<T extends RecordFieldsMap> extends EventEmitter
|
|
|
58
59
|
let date = moment.default(fieldData[key], this.layout.database.host.metadata.productInfo.timeFormat.replace("dd", "DD"))
|
|
59
60
|
.utcOffset(this.layout.database.host.timezoneOffset, true)
|
|
60
61
|
.local()
|
|
61
|
-
_field.set(date
|
|
62
|
+
_field.set(date)
|
|
62
63
|
_field.edited = false
|
|
63
64
|
}
|
|
64
65
|
else if (_field.metadata.result === "date") {
|
|
@@ -66,7 +67,7 @@ export abstract class RecordBase<T extends RecordFieldsMap> extends EventEmitter
|
|
|
66
67
|
let date = moment.default(fieldData[key], this.layout.database.host.metadata.productInfo.dateFormat.replace("dd", "DD"))
|
|
67
68
|
.utcOffset(this.layout.database.host.timezoneOffset, true)
|
|
68
69
|
.local()
|
|
69
|
-
_field.set(date
|
|
70
|
+
_field.set(date)
|
|
70
71
|
_field.edited = false
|
|
71
72
|
}
|
|
72
73
|
}
|
package/src/types.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export type PickPortals<LAYOUT extends LayoutInterface, PORTALS extends string |
|
|
|
8
8
|
};
|
|
9
9
|
export interface databaseOptionsBase {
|
|
10
10
|
database: string;
|
|
11
|
+
debug?: boolean;
|
|
11
12
|
credentials: loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris | loginOptionsToken;
|
|
12
13
|
}
|
|
13
14
|
export interface databaseOptionsWithExternalSources extends databaseOptionsBase {
|
package/src/types.ts
CHANGED
|
@@ -12,7 +12,8 @@ export type PickPortals<LAYOUT extends LayoutInterface, PORTALS extends string |
|
|
|
12
12
|
Omit<LAYOUT, "portals"> & { portals: Pick<LAYOUT["portals"], PORTALS> }
|
|
13
13
|
|
|
14
14
|
export interface databaseOptionsBase {
|
|
15
|
-
database: string
|
|
15
|
+
database: string,
|
|
16
|
+
debug?: boolean,
|
|
16
17
|
credentials: loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris | loginOptionsToken,
|
|
17
18
|
}
|
|
18
19
|
|
package/src/utils/query.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
+
import { Moment } from "moment";
|
|
1
2
|
export declare const FindRequestSymbol: unique symbol;
|
|
2
|
-
type QueryParameter = string | number |
|
|
3
|
+
type QueryParameter = string | number | Moment;
|
|
3
4
|
export type Query = {
|
|
4
|
-
[FindRequestSymbol]: string;
|
|
5
|
+
[FindRequestSymbol]: (string | Moment)[];
|
|
5
6
|
};
|
|
6
7
|
export declare function queryEscape(str: string): string;
|
|
7
|
-
export declare function query(strings: TemplateStringsArray, ...args: QueryParameter[]):
|
|
8
|
-
[FindRequestSymbol]: string;
|
|
9
|
-
};
|
|
8
|
+
export declare function query(strings: TemplateStringsArray, ...args: QueryParameter[]): Query;
|
|
10
9
|
export {};
|
package/src/utils/query.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Copyright (c) 2024. See LICENSE file for more information
|
|
3
3
|
*/
|
|
4
|
+
import moment from "moment/moment.js";
|
|
4
5
|
export const FindRequestSymbol = Symbol("easyfm-findrequest");
|
|
5
6
|
const SPECIAL_CHARACTERS = ["\\", "=", "<", "≤", "≥", ">", "…", "...", "//", "@", "#", "*", "\"", "~"];
|
|
6
7
|
export function queryEscape(str) {
|
|
@@ -11,11 +12,17 @@ export function queryEscape(str) {
|
|
|
11
12
|
}
|
|
12
13
|
export function query(strings, ...args) {
|
|
13
14
|
let argStrings = args.map(item => {
|
|
14
|
-
|
|
15
|
+
if (moment.isMoment(item))
|
|
16
|
+
return item;
|
|
17
|
+
else if (typeof item === "number") {
|
|
18
|
+
return queryEscape(item.toString());
|
|
19
|
+
}
|
|
20
|
+
else
|
|
21
|
+
return queryEscape(item);
|
|
15
22
|
});
|
|
16
23
|
// Zip the parameters together
|
|
17
24
|
const query = strings.map((str, index) => {
|
|
18
|
-
return str
|
|
19
|
-
});
|
|
20
|
-
return { [FindRequestSymbol]: query
|
|
25
|
+
return [str, (argStrings[index] || '')];
|
|
26
|
+
}).flat(1);
|
|
27
|
+
return { [FindRequestSymbol]: query };
|
|
21
28
|
}
|
package/src/utils/query.ts
CHANGED
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
* Copyright (c) 2024. See LICENSE file for more information
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import {Moment} from "moment";
|
|
6
|
+
import moment from "moment/moment.js";
|
|
7
|
+
|
|
5
8
|
export const FindRequestSymbol = Symbol("easyfm-findrequest");
|
|
6
9
|
const SPECIAL_CHARACTERS = ["\\", "=", "<", "≤", "≥", ">", "…", "...", "//", "@", "#", "*", "\"", "~"]
|
|
7
|
-
type QueryParameter = string | number |
|
|
8
|
-
export type Query = {[FindRequestSymbol]: string}
|
|
10
|
+
type QueryParameter = string | number | Moment
|
|
11
|
+
export type Query = {[FindRequestSymbol]: (string | Moment)[]}
|
|
9
12
|
|
|
10
13
|
export function queryEscape(str: string) {
|
|
11
14
|
for (let char of SPECIAL_CHARACTERS) {
|
|
@@ -14,14 +17,18 @@ export function queryEscape(str: string) {
|
|
|
14
17
|
return str
|
|
15
18
|
}
|
|
16
19
|
|
|
17
|
-
export function query(strings: TemplateStringsArray, ...args: QueryParameter[]) {
|
|
20
|
+
export function query(strings: TemplateStringsArray, ...args: QueryParameter[]): Query {
|
|
18
21
|
let argStrings = args.map(item => {
|
|
19
|
-
|
|
22
|
+
if (moment.isMoment(item)) return item
|
|
23
|
+
else if (typeof item === "number") {
|
|
24
|
+
return queryEscape(item.toString())
|
|
25
|
+
}
|
|
26
|
+
else return queryEscape(item)
|
|
20
27
|
})
|
|
21
28
|
|
|
22
29
|
// Zip the parameters together
|
|
23
30
|
const query = strings.map((str, index) => {
|
|
24
|
-
return str
|
|
25
|
-
})
|
|
26
|
-
return {[FindRequestSymbol]: query
|
|
31
|
+
return [str, (argStrings[index] || '')]
|
|
32
|
+
}).flat(1)
|
|
33
|
+
return {[FindRequestSymbol]: query}
|
|
27
34
|
}
|
package/src/bin/example.xml
DELETED
|
Binary file
|