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

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jd-data-limited/easy-fm",
3
- "version": "4.0.14",
3
+ "version": "4.0.16",
4
4
  "dependencies": {
5
5
  "follow-redirects": "^1.15.5",
6
6
  "@types/follow-redirects": "^1.14.4",
package/src/bin/cli.js CHANGED
@@ -1,17 +1,19 @@
1
1
  #!/usr/bin/env node
2
+ "use strict";
2
3
  /*
3
4
  * Copyright (c) 2024. See LICENSE file for more information
4
5
  */
5
- import { Command } from "commander";
6
- import { generateTypesCLI } from "./generateTypes.js";
7
- const program = new Command();
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();
8
10
  program
9
11
  .name("easyfm")
10
12
  .description("A NodeJS wrapper for the FileMaker Data CLI");
11
13
  program
12
14
  .command("generate-types")
13
15
  .description("Automatically generate layout interfaces from your database")
14
- .action(() => {
15
- generateTypesCLI();
16
+ .action(function () {
17
+ (0, generateTypes_js_1.generateTypesCLI)();
16
18
  });
17
19
  program.parse(process.argv);
@@ -1,10 +1,53 @@
1
+ "use strict";
1
2
  /*
2
3
  * Copyright (c) 2024. See LICENSE file for more information
3
4
  */
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";
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");
8
51
  function sub(str) {
9
52
  return str
10
53
  .replace(/[^a-zA-Z0-9_]/g, "") // Remove non-alphanumeric characters
@@ -12,38 +55,42 @@ function sub(str) {
12
55
  }
13
56
  function generateFieldType(field) {
14
57
  switch (field.result) {
15
- case ApiFieldResultTypes.TEXT:
58
+ case apiResults_js_1.ApiFieldResultTypes.TEXT:
16
59
  return "Field<string>";
17
- case ApiFieldResultTypes.CONTAINER:
60
+ case apiResults_js_1.ApiFieldResultTypes.CONTAINER:
18
61
  return "Field<Container>";
19
- case ApiFieldResultTypes.DATE:
62
+ case apiResults_js_1.ApiFieldResultTypes.DATE:
20
63
  return "Field<Date>";
21
- case ApiFieldResultTypes.NUMBER:
64
+ case apiResults_js_1.ApiFieldResultTypes.NUMBER:
22
65
  return "Field<number>";
23
- case ApiFieldResultTypes.TIME:
66
+ case apiResults_js_1.ApiFieldResultTypes.TIME:
24
67
  return "Field<Date>";
25
- case ApiFieldResultTypes.TIMESTAMP:
68
+ case apiResults_js_1.ApiFieldResultTypes.TIMESTAMP:
26
69
  return "Field<Date>";
27
70
  }
28
71
  }
29
72
  function processFieldsChunk(fields) {
30
- let existing_fields = [];
31
- fields = fields.filter(field => {
73
+ var existing_fields = [];
74
+ fields = fields.filter(function (field) {
32
75
  if (existing_fields.includes(field.name))
33
76
  return false;
34
77
  existing_fields.push(field.name);
35
78
  return true;
36
79
  });
37
- return fields.map(field => {
80
+ return fields.map(function (field) {
38
81
  return JSON.stringify(field.name) + ": " + generateFieldType(field);
39
82
  });
40
83
  }
41
84
  function camelToSnakeCase(str) {
42
85
  return str.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();
43
86
  }
44
- function safeStringInjection(str, ...args) {
45
- let result = "";
46
- for (let i = 0; i < str.length; i++) {
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++) {
47
94
  result += str[i];
48
95
  if (i < args.length) {
49
96
  result += JSON.stringify(args[i].toString());
@@ -51,111 +98,158 @@ function safeStringInjection(str, ...args) {
51
98
  }
52
99
  return result;
53
100
  }
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://";
67
- }
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://");
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 substituteName, metadata, fields, portals, portalsProcessed, interfaceName;
179
+ return __generator(this, function (_d) {
180
+ switch (_d.label) {
181
+ case 0:
182
+ substituteName = sub(layout.name);
183
+ if (!substituteName)
184
+ return [2 /*return*/, "continue"];
185
+ console.log(substituteName);
186
+ return [4 /*yield*/, layout.getLayoutMeta()];
187
+ case 1:
188
+ metadata = _d.sent();
189
+ fields = processFieldsChunk(metadata.fieldMetaData);
190
+ portals = metadata.portalMetaData;
191
+ portalsProcessed = Object.keys(portals).map(function (portalName) {
192
+ return "".concat(JSON.stringify(portalName), ": Portal<{").concat(processFieldsChunk(portals[portalName]), "}>");
193
+ });
194
+ interfaceName = substituteName + "LayoutInterface";
195
+ layoutInterfaces.set(layout.name, {
196
+ interfaceName: interfaceName,
197
+ interface: "export interface ".concat(interfaceName, " extends LayoutInterface {\n fields: {").concat(fields.join(",\n "), "},\n portals: {").concat(portalsProcessed.join(",\n "), "}\n }")
198
+ });
199
+ return [2 /*return*/];
200
+ }
201
+ });
202
+ };
203
+ _i = 0, layouts_1 = layouts;
204
+ _c.label = 4;
205
+ case 4:
206
+ if (!(_i < layouts_1.length)) return [3 /*break*/, 7];
207
+ layout = layouts_1[_i];
208
+ return [5 /*yield**/, _loop_1(layout)];
209
+ case 5:
210
+ _c.sent();
211
+ _c.label = 6;
212
+ case 6:
213
+ _i++;
214
+ return [3 /*break*/, 4];
215
+ case 7:
216
+ layoutInterfaceLinks = [];
217
+ for (_a = 0, layoutInterfaces_1 = layoutInterfaces; _a < layoutInterfaces_1.length; _a++) {
218
+ layout = layoutInterfaces_1[_a];
219
+ layoutInterfaceLinks.push([
220
+ "".concat(JSON.stringify(layout[0]), ": ").concat(layout[1].interfaceName),
221
+ "".concat(sub(camelToSnakeCase(layout[0]).toUpperCase().replace(" ", "_")), ": HOST.getLayout<").concat(layout[1].interfaceName, ">(").concat(JSON.stringify(layout[0]), ")")
222
+ ]);
223
+ }
224
+ stream.write(safeStringInjection(templateObject_1 || (templateObject_1 = __makeTemplateObject(["const HOST = new FMHost(", ", ", ", ", ");"], ["const HOST = new FMHost(", ", ", ", ", ");"])), data.hostname, data.timezoneOffset, data.verify));
225
+ stream.write("HOST.database<{layouts:{".concat(layoutInterfaceLinks.map(function (i) { return i[0]; }).join(","), "}}>();"));
226
+ stream.write("\n\n");
227
+ stream.write("const LAYOUTS = {".concat(layoutInterfaceLinks.map(function (i) { return i[1]; }).join(","), "};"));
228
+ for (_b = 0, layoutInterfaces_2 = layoutInterfaces; _b < layoutInterfaces_2.length; _b++) {
229
+ layout = layoutInterfaces_2[_b];
230
+ stream.write("\n\n");
231
+ stream.write(layout[1].interface + ";");
232
+ }
233
+ // StructureKind
234
+ //
235
+ // const project = new Project({
236
+ // tsConfigFilePath: "../tsconfig.json"
237
+ // })
238
+ // project.resolveSourceFileDependencies()
239
+ return [4 /*yield*/, DATABASE.logout()];
240
+ case 8:
241
+ // StructureKind
242
+ //
243
+ // const project = new Project({
244
+ // tsConfigFilePath: "../tsconfig.json"
245
+ // })
246
+ // project.resolveSourceFileDependencies()
247
+ _c.sent();
248
+ console.log("DONE! Wrote database types to ./types.ts");
249
+ return [2 /*return*/];
82
250
  }
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: []
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
251
  });
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");
252
+ });
161
253
  }
254
+ exports.generateTypesCLI = generateTypesCLI;
255
+ var templateObject_1;
@@ -2,13 +2,17 @@ import { Database } from "./database.js";
2
2
  import { HostBase } from "./HostBase.js";
3
3
  import { databaseOptionsWithExternalSources, FMHostMetadata, loginOptionsClaris, loginOptionsFileMaker, loginOptionsOAuth } from "../types.js";
4
4
  import { DatabaseStructure } from "../databaseStructure.js";
5
+ import { Moment } from "moment";
5
6
  export default class FMHost implements HostBase {
6
7
  readonly hostname: string;
7
- readonly timezoneOffset: number;
8
+ readonly timezoneOffsetFunc: (moment: Moment) => number;
8
9
  readonly verify: boolean;
9
10
  _metadata: FMHostMetadata | null;
10
- constructor(_hostname: string, timezoneOffset?: number, verify?: boolean);
11
+ constructor(_hostname: string, timezoneOffset?: (moment: Moment) => number, verify?: boolean);
11
12
  get metadata(): FMHostMetadata;
13
+ get dateFormat(): string;
14
+ get timeFormat(): string;
15
+ get timeStampFormat(): string;
12
16
  listDatabases(credentials?: loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris): Promise<any>;
13
17
  database<T extends DatabaseStructure>(data: databaseOptionsWithExternalSources): Database<T>;
14
18
  getMetadata(): Promise<FMHostMetadata>;
@@ -6,14 +6,14 @@ import { FMError } from "../FMError.js";
6
6
  import { Database } from "./database.js";
7
7
  export default class FMHost {
8
8
  hostname;
9
- timezoneOffset;
9
+ timezoneOffsetFunc;
10
10
  verify;
11
11
  _metadata = null;
12
- constructor(_hostname, timezoneOffset = 0 - (new Date()).getTimezoneOffset(), verify = true) {
12
+ constructor(_hostname, timezoneOffset = (moment) => 0 - (new Date()).getTimezoneOffset(), verify = true) {
13
13
  if (!(/^https?:\/\//).test(_hostname))
14
14
  throw "hostname MUST begin with either http:// or https://";
15
15
  this.hostname = _hostname;
16
- this.timezoneOffset = timezoneOffset;
16
+ this.timezoneOffsetFunc = timezoneOffset;
17
17
  this.verify = verify;
18
18
  }
19
19
  get metadata() {
@@ -22,12 +22,23 @@ export default class FMHost {
22
22
  buildDate: new Date(),
23
23
  name: "",
24
24
  version: "",
25
- dateFormat: "",
26
- timeFormat: "",
27
- timeStampFormat: ""
25
+ dateFormat: "MM/dd/yyyy",
26
+ timeFormat: "HH:mm:ss",
27
+ timeStampFormat: "MM/dd/yyyy HH:mm:ss"
28
28
  }
29
29
  };
30
30
  }
31
+ get dateFormat() {
32
+ return this.metadata.productInfo.dateFormat
33
+ .replace("dd", "DD")
34
+ .replace("yyyy", "YYYY");
35
+ }
36
+ get timeFormat() { return this.metadata.productInfo.timeFormat; }
37
+ get timeStampFormat() {
38
+ return this.metadata.productInfo.timeStampFormat
39
+ .replace("dd", "DD")
40
+ .replace("yyyy", "YYYY");
41
+ }
31
42
  async listDatabases(credentials) {
32
43
  let headers = {};
33
44
  if (credentials) {
@@ -15,17 +15,22 @@ import {
15
15
  } from "../types.js";
16
16
  import {ApiResults} from "../models/apiResults.js";
17
17
  import {DatabaseStructure} from "../databaseStructure.js";
18
+ import {Moment} from "moment";
18
19
 
19
20
  export default class FMHost implements HostBase {
20
21
  readonly hostname: string
21
- readonly timezoneOffset: number
22
+ readonly timezoneOffsetFunc: (moment: Moment) => number
22
23
  readonly verify: boolean
23
24
  _metadata: FMHostMetadata | null = null
24
25
 
25
- constructor(_hostname: string, timezoneOffset = 0 - (new Date()).getTimezoneOffset(), verify = true) {
26
+ constructor(
27
+ _hostname: string,
28
+ timezoneOffset = (moment: Moment) => 0 - (new Date()).getTimezoneOffset(),
29
+ verify = true
30
+ ) {
26
31
  if (!(/^https?:\/\//).test(_hostname)) throw "hostname MUST begin with either http:// or https://"
27
32
  this.hostname = _hostname
28
- this.timezoneOffset = timezoneOffset
33
+ this.timezoneOffsetFunc = timezoneOffset
29
34
  this.verify = verify
30
35
  }
31
36
 
@@ -35,13 +40,26 @@ export default class FMHost implements HostBase {
35
40
  buildDate: new Date(),
36
41
  name: "",
37
42
  version: "",
38
- dateFormat: "",
39
- timeFormat: "",
40
- timeStampFormat: ""
43
+ dateFormat: "MM/dd/yyyy",
44
+ timeFormat: "HH:mm:ss",
45
+ timeStampFormat: "MM/dd/yyyy HH:mm:ss"
41
46
  }
42
47
  };
43
48
  }
44
49
 
50
+ get dateFormat() {
51
+ return this.metadata.productInfo.dateFormat
52
+ .replace("dd", "DD")
53
+ .replace("yyyy", "YYYY")
54
+ }
55
+
56
+ get timeFormat() {return this.metadata.productInfo.timeFormat}
57
+ get timeStampFormat() {
58
+ return this.metadata.productInfo.timeStampFormat
59
+ .replace("dd", "DD")
60
+ .replace("yyyy", "YYYY")
61
+ }
62
+
45
63
  async listDatabases(credentials?: loginOptionsOAuth | loginOptionsFileMaker | loginOptionsClaris) {
46
64
  let headers = {}
47
65
  if (credentials) {
@@ -1,8 +1,12 @@
1
1
  import { FMHostMetadata } from "../types.js";
2
+ import { Moment } from "moment";
2
3
  export interface HostBase {
3
4
  readonly hostname: string;
4
- readonly timezoneOffset: number;
5
+ readonly timezoneOffsetFunc: (moment: Moment) => number;
5
6
  readonly verify: boolean;
6
7
  metadata: FMHostMetadata;
7
8
  getMetadata(): PromiseLike<any>;
9
+ timeFormat: string;
10
+ dateFormat: string;
11
+ timeStampFormat: string;
8
12
  }
@@ -3,11 +3,15 @@
3
3
  */
4
4
 
5
5
  import {FMHostMetadata} from "../types.js";
6
+ import {Moment} from "moment";
6
7
 
7
8
  export interface HostBase {
8
9
  readonly hostname: string
9
- readonly timezoneOffset: number
10
+ readonly timezoneOffsetFunc: (moment: Moment) => number,
10
11
  readonly verify: boolean
11
12
  metadata: FMHostMetadata
12
13
  getMetadata(): PromiseLike<any>
14
+ timeFormat: string
15
+ dateFormat: string
16
+ timeStampFormat: string
13
17
  }
package/src/index.d.ts CHANGED
@@ -12,7 +12,7 @@ import { Portal } from "./records/portal.js";
12
12
  import { FindRequest, FindRequestRaw, RecordGetOperation } from "./records/getOperations/recordGetOperation.js";
13
13
  import { LayoutRecordManager } from "./layouts/layoutRecordManager.js";
14
14
  import { RecordFieldsMap } from "./layouts/recordFieldsMap.js";
15
- import { query, queryEscape } from "./utils/query.js";
15
+ import { asDate, asTime, asTimestamp, query, queryEscape } from "./utils/query.js";
16
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, Field, RecordGetOperation, LayoutRecordManager, Container, PickPortals, FindRequestRaw, FindRequest, query, queryEscape };
18
+ export { FMError, TYPES, Database, Layout, RecordBase, LayoutRecord, LayoutInterface, RecordFieldsMap, PortalInterface, PortalRecord, Portal, Field, RecordGetOperation, LayoutRecordManager, Container, PickPortals, FindRequestRaw, FindRequest, query, queryEscape, asDate, asTime, asTimestamp };
package/src/index.js CHANGED
@@ -12,7 +12,7 @@ import { PortalRecord } from "./records/portalRecord.js";
12
12
  import { Portal } from "./records/portal.js";
13
13
  import { RecordGetOperation } from "./records/getOperations/recordGetOperation.js";
14
14
  import { LayoutRecordManager } from "./layouts/layoutRecordManager.js";
15
- import { query, queryEscape } from "./utils/query.js";
15
+ import { asDate, asTime, asTimestamp, query, queryEscape } from "./utils/query.js";
16
16
  import { Field } from "./records/field.js";
17
17
  export default FMHost;
18
- export { FMError, TYPES, Database, Layout, RecordBase, LayoutRecord, PortalRecord, Portal, Field, RecordGetOperation, LayoutRecordManager, query, queryEscape };
18
+ export { FMError, TYPES, Database, Layout, RecordBase, LayoutRecord, PortalRecord, Portal, Field, RecordGetOperation, LayoutRecordManager, query, queryEscape, asDate, asTime, asTimestamp };
package/src/index.ts CHANGED
@@ -16,7 +16,7 @@ import {Portal} from "./records/portal.js";
16
16
  import {FindRequest, FindRequestRaw, RecordGetOperation} from "./records/getOperations/recordGetOperation.js";
17
17
  import {LayoutRecordManager} from "./layouts/layoutRecordManager.js";
18
18
  import {RecordFieldsMap} from "./layouts/recordFieldsMap.js";
19
- import {query, queryEscape} from "./utils/query.js";
19
+ import {asDate, asTime, asTimestamp, query, queryEscape} from "./utils/query.js";
20
20
  import {Container, Field} from "./records/field.js";
21
21
 
22
22
  export default FMHost
@@ -40,5 +40,8 @@ export {
40
40
  FindRequestRaw,
41
41
  FindRequest,
42
42
  query,
43
- queryEscape
43
+ queryEscape,
44
+ asDate,
45
+ asTime,
46
+ asTimestamp
44
47
  }
@@ -112,9 +112,14 @@ export class RecordGetOperation {
112
112
  return item;
113
113
  // Re-write date into correct format
114
114
  return item
115
+ .moment
115
116
  .clone()
116
- .utcOffset(this.layout.database.host.timezoneOffset)
117
- .format("MM/DD/YYYY HH:mm:ss");
117
+ .utcOffset(this.layout.database.host.timezoneOffsetFunc(item.moment))
118
+ .format(item.type === "date" ?
119
+ this.layout.database.host.dateFormat :
120
+ item.type == "time" ?
121
+ this.layout.database.host.timeFormat :
122
+ this.layout.database.host.timeStampFormat);
118
123
  }).join("");
119
124
  }
120
125
  return out;
@@ -41,7 +41,7 @@ export class RecordGetOperation<T extends LayoutInterface, OPTIONS extends GetOp
41
41
  protected sortData: { fieldName: string, sortOrder: SortOrder }[] = []
42
42
  protected portals: Partial<PortalData<T>>
43
43
  protected offset: number = 1
44
- protected queries: {req: FindRequestRaw, omit: boolean}[] = []
44
+ protected queries: { req: FindRequestRaw, omit: boolean }[] = []
45
45
 
46
46
  constructor(layout: LayoutBase, options: OPTIONS) {
47
47
  this.layout = layout
@@ -60,7 +60,9 @@ export class RecordGetOperation<T extends LayoutInterface, OPTIONS extends GetOp
60
60
  let out: any = {}
61
61
  for (let key of Object.keys(query.req)) {
62
62
  if (query.req[key]) out[key] = query.req[key]
63
- else {out[key] = query.req[key]}
63
+ else {
64
+ out[key] = query.req[key]
65
+ }
64
66
  }
65
67
  if (query.omit) out.omit = "true"
66
68
  return out
@@ -69,7 +71,7 @@ export class RecordGetOperation<T extends LayoutInterface, OPTIONS extends GetOp
69
71
  }
70
72
 
71
73
  protected generateParamsBody(offset: number, limit: number) {
72
- const params: {[key: string]: any} = {
74
+ const params: { [key: string]: any } = {
73
75
  limit: limit.toString(),
74
76
  offset: offset.toString(),
75
77
  dateformats: 2 // Ensure dates are received in ISO8601 format
@@ -97,6 +99,7 @@ export class RecordGetOperation<T extends LayoutInterface, OPTIONS extends GetOp
97
99
 
98
100
  return params
99
101
  }
102
+
100
103
  protected generateParamsURL(offset: number, limit: number) {
101
104
  const params = new URLSearchParams({
102
105
  _limit: limit.toString(),
@@ -134,7 +137,7 @@ export class RecordGetOperation<T extends LayoutInterface, OPTIONS extends GetOp
134
137
  return this
135
138
  }
136
139
 
137
- private parseFindRequest<I extends FindRequest>(query: I): {[key in keyof I]: string} {
140
+ private parseFindRequest<I extends FindRequest>(query: I): { [key in keyof I]: string } {
138
141
  let out: any = {}
139
142
  for (let key of Object.keys(query)) {
140
143
  out[key] = query[key][FindRequestSymbol].map(item => {
@@ -142,9 +145,16 @@ export class RecordGetOperation<T extends LayoutInterface, OPTIONS extends GetOp
142
145
 
143
146
  // Re-write date into correct format
144
147
  return item
148
+ .moment
145
149
  .clone()
146
- .utcOffset(this.layout.database.host.timezoneOffset)
147
- .format("MM/DD/YYYY HH:mm:ss")
150
+ .utcOffset(this.layout.database.host.timezoneOffsetFunc(item.moment))
151
+ .format(
152
+ item.type === "date" ?
153
+ this.layout.database.host.dateFormat :
154
+ item.type == "time" ?
155
+ this.layout.database.host.timeFormat :
156
+ this.layout.database.host.timeStampFormat
157
+ )
148
158
  }).join("")
149
159
  }
150
160
  return out
@@ -189,7 +199,7 @@ export class RecordGetOperation<T extends LayoutInterface, OPTIONS extends GetOp
189
199
  else {
190
200
  throw new FMError(res.messages[0].code, res.httpStatus, res, trace)
191
201
  }
192
- } catch(e) {
202
+ } catch (e) {
193
203
  if (e instanceof FMError) {
194
204
  if (e.code === 401) {
195
205
  // No records found, so return empty set
@@ -160,18 +160,19 @@ export class LayoutRecord extends RecordBase {
160
160
  let value = field.value;
161
161
  if (value instanceof Date) {
162
162
  // @ts-ignore
163
- let _value = moment.default(value)
164
- .utcOffset(this.layout.database.host.timezoneOffset);
163
+ let _value = moment.default(value);
164
+ _value = _value
165
+ .utcOffset(this.layout.database.host.timezoneOffsetFunc(_value));
165
166
  // @ts-ignore
166
167
  switch (field.metadata.result) {
167
168
  case "time":
168
- value = _value.format(this.layout.database.host.metadata.productInfo.timeFormat.replace("dd", "DD"));
169
+ value = _value.format(this.layout.database.host.timeFormat);
169
170
  break;
170
171
  case "date":
171
- value = _value.format(this.layout.database.host.metadata.productInfo.dateFormat.replace("dd", "DD"));
172
+ value = _value.format(this.layout.database.host.dateFormat);
172
173
  break;
173
174
  default:
174
- value = _value.format(this.layout.database.host.metadata.productInfo.timeStampFormat.replace("dd", "DD"));
175
+ value = _value.format(this.layout.database.host.timeStampFormat);
175
176
  }
176
177
  }
177
178
  fields_processed[field.id] = value;
@@ -190,19 +190,20 @@ export class LayoutRecord<LAYOUT extends LayoutInterface> extends RecordBase<LAY
190
190
  if (value instanceof Date) {
191
191
  // @ts-ignore
192
192
  let _value = moment.default(value)
193
- .utcOffset(this.layout.database.host.timezoneOffset)
193
+ _value = _value
194
+ .utcOffset(this.layout.database.host.timezoneOffsetFunc(_value))
194
195
 
195
196
  // @ts-ignore
196
197
 
197
198
  switch (field.metadata.result) {
198
199
  case "time":
199
- value = _value.format(this.layout.database.host.metadata.productInfo.timeFormat.replace("dd", "DD"))
200
+ value = _value.format(this.layout.database.host.timeFormat)
200
201
  break
201
202
  case "date":
202
- value = _value.format(this.layout.database.host.metadata.productInfo.dateFormat.replace("dd", "DD"))
203
+ value = _value.format(this.layout.database.host.dateFormat)
203
204
  break
204
205
  default:
205
- value = _value.format(this.layout.database.host.metadata.productInfo.timeStampFormat.replace("dd", "DD"))
206
+ value = _value.format(this.layout.database.host.timeStampFormat)
206
207
  }
207
208
  }
208
209
  fields_processed[field.id] = value
@@ -35,24 +35,27 @@ export class RecordBase extends EventEmitter {
35
35
  if (!!fieldData[key]) {
36
36
  if (_field.metadata.result === "timeStamp") {
37
37
  // @ts-ignore
38
- let date = moment.default(fieldData[key], this.layout.database.host.metadata.productInfo.timeStampFormat.replace("dd", "DD"))
39
- .utcOffset(this.layout.database.host.timezoneOffset, true)
38
+ let date = moment.default(fieldData[key]);
39
+ date = date
40
+ .utcOffset(this.layout.database.host.timezoneOffsetFunc(date), true)
40
41
  .local();
41
42
  _field.set(date);
42
43
  _field.edited = false;
43
44
  }
44
45
  else if (_field.metadata.result === "time") {
45
46
  // @ts-ignore
46
- let date = moment.default(fieldData[key], this.layout.database.host.metadata.productInfo.timeFormat.replace("dd", "DD"))
47
- .utcOffset(this.layout.database.host.timezoneOffset, true)
47
+ let date = moment.default(fieldData[key]);
48
+ date = date
49
+ .utcOffset(this.layout.database.host.timezoneOffsetFunc(date), true)
48
50
  .local();
49
51
  _field.set(date);
50
52
  _field.edited = false;
51
53
  }
52
54
  else if (_field.metadata.result === "date") {
53
55
  // @ts-ignore
54
- let date = moment.default(fieldData[key], this.layout.database.host.metadata.productInfo.dateFormat.replace("dd", "DD"))
55
- .utcOffset(this.layout.database.host.timezoneOffset, true)
56
+ let date = moment.default(fieldData[key]);
57
+ date = date
58
+ .utcOffset(this.layout.database.host.timezoneOffsetFunc(date), true)
56
59
  .local();
57
60
  _field.set(date);
58
61
  _field.edited = false;
@@ -47,8 +47,9 @@ export abstract class RecordBase<T extends RecordFieldsMap> extends EventEmitter
47
47
  if (!!fieldData[key]) {
48
48
  if (_field.metadata.result === "timeStamp") {
49
49
  // @ts-ignore
50
- let date = moment.default(fieldData[key], this.layout.database.host.metadata.productInfo.timeStampFormat.replace("dd", "DD"))
51
- .utcOffset(this.layout.database.host.timezoneOffset, true)
50
+ let date = moment.default(fieldData[key])
51
+ date = date
52
+ .utcOffset(this.layout.database.host.timezoneOffsetFunc(date), true)
52
53
  .local()
53
54
  _field.set(date)
54
55
  _field.edited = false
@@ -56,16 +57,18 @@ export abstract class RecordBase<T extends RecordFieldsMap> extends EventEmitter
56
57
  }
57
58
  else if (_field.metadata.result === "time") {
58
59
  // @ts-ignore
59
- let date = moment.default(fieldData[key], this.layout.database.host.metadata.productInfo.timeFormat.replace("dd", "DD"))
60
- .utcOffset(this.layout.database.host.timezoneOffset, true)
60
+ let date = moment.default(fieldData[key])
61
+ date = date
62
+ .utcOffset(this.layout.database.host.timezoneOffsetFunc(date), true)
61
63
  .local()
62
64
  _field.set(date)
63
65
  _field.edited = false
64
66
  }
65
67
  else if (_field.metadata.result === "date") {
66
68
  // @ts-ignore
67
- let date = moment.default(fieldData[key], this.layout.database.host.metadata.productInfo.dateFormat.replace("dd", "DD"))
68
- .utcOffset(this.layout.database.host.timezoneOffset, true)
69
+ let date = moment.default(fieldData[key])
70
+ date = date
71
+ .utcOffset(this.layout.database.host.timezoneOffsetFunc(date), true)
69
72
  .local()
70
73
  _field.set(date)
71
74
  _field.edited = false
@@ -1,9 +1,16 @@
1
1
  import { Moment } from "moment";
2
2
  export declare const FindRequestSymbol: unique symbol;
3
- type QueryParameter = string | number | Moment;
3
+ export type TimestampType = {
4
+ type: "date" | "time" | "timestamp";
5
+ moment: Moment;
6
+ };
7
+ type QueryParameter = string | number | TimestampType;
4
8
  export type Query = {
5
- [FindRequestSymbol]: (string | Moment)[];
9
+ [FindRequestSymbol]: (string | TimestampType)[];
6
10
  };
7
11
  export declare function queryEscape(str: string): string;
8
12
  export declare function query(strings: TemplateStringsArray, ...args: QueryParameter[]): Query;
13
+ export declare function asDate(date: Date | Moment): TimestampType;
14
+ export declare function asTime(date: Date | Moment): TimestampType;
15
+ export declare function asTimestamp(date: Date | Moment): TimestampType;
9
16
  export {};
@@ -1,7 +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
+ import moment from "moment";
5
5
  export const FindRequestSymbol = Symbol("easyfm-findrequest");
6
6
  const SPECIAL_CHARACTERS = ["\\", "=", "<", "≤", "≥", ">", "…", "...", "//", "@", "#", "*", "\"", "~"];
7
7
  export function queryEscape(str) {
@@ -12,13 +12,13 @@ export function queryEscape(str) {
12
12
  }
13
13
  export function query(strings, ...args) {
14
14
  let argStrings = args.map(item => {
15
- if (moment.isMoment(item))
16
- return item;
17
- else if (typeof item === "number") {
15
+ if (typeof item === "number") {
18
16
  return queryEscape(item.toString());
19
17
  }
20
- else
18
+ else if (typeof item === "string")
21
19
  return queryEscape(item);
20
+ else
21
+ return item;
22
22
  });
23
23
  // Zip the parameters together
24
24
  const query = strings.map((str, index) => {
@@ -26,3 +26,22 @@ export function query(strings, ...args) {
26
26
  }).flat(1);
27
27
  return { [FindRequestSymbol]: query };
28
28
  }
29
+ export function asDate(date) {
30
+ return {
31
+ type: "date",
32
+ moment: moment(date)
33
+ };
34
+ }
35
+ export function asTime(date) {
36
+ return {
37
+ type: "time",
38
+ moment: moment(date)
39
+ };
40
+ }
41
+ export function asTimestamp(date) {
42
+ return {
43
+ type: "timestamp",
44
+ moment: moment(date)
45
+ };
46
+ }
47
+ query;
@@ -2,13 +2,16 @@
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";
5
+ import moment, {Moment} from "moment";
7
6
 
8
7
  export const FindRequestSymbol = Symbol("easyfm-findrequest");
9
8
  const SPECIAL_CHARACTERS = ["\\", "=", "<", "≤", "≥", ">", "…", "...", "//", "@", "#", "*", "\"", "~"]
10
- type QueryParameter = string | number | Moment
11
- export type Query = {[FindRequestSymbol]: (string | Moment)[]}
9
+ export type TimestampType = {
10
+ type: "date" | "time" | "timestamp",
11
+ moment: Moment
12
+ }
13
+ type QueryParameter = string | number | TimestampType
14
+ export type Query = {[FindRequestSymbol]: (string | TimestampType)[]}
12
15
 
13
16
  export function queryEscape(str: string) {
14
17
  for (let char of SPECIAL_CHARACTERS) {
@@ -19,11 +22,11 @@ export function queryEscape(str: string) {
19
22
 
20
23
  export function query(strings: TemplateStringsArray, ...args: QueryParameter[]): Query {
21
24
  let argStrings = args.map(item => {
22
- if (moment.isMoment(item)) return item
23
- else if (typeof item === "number") {
25
+ if (typeof item === "number") {
24
26
  return queryEscape(item.toString())
25
27
  }
26
- else return queryEscape(item)
28
+ else if (typeof item === "string") return queryEscape(item)
29
+ else return item
27
30
  })
28
31
 
29
32
  // Zip the parameters together
@@ -31,4 +34,27 @@ export function query(strings: TemplateStringsArray, ...args: QueryParameter[]):
31
34
  return [str, (argStrings[index] || '')]
32
35
  }).flat(1)
33
36
  return {[FindRequestSymbol]: query}
34
- }
37
+ }
38
+
39
+ export function asDate(date: Date | Moment): TimestampType {
40
+ return {
41
+ type: "date",
42
+ moment: moment(date)
43
+ }
44
+ }
45
+
46
+ export function asTime(date: Date | Moment): TimestampType {
47
+ return {
48
+ type: "time",
49
+ moment: moment(date)
50
+ }
51
+ }
52
+
53
+ export function asTimestamp(date: Date | Moment): TimestampType {
54
+ return {
55
+ type: "timestamp",
56
+ moment: moment(date)
57
+ }
58
+ }
59
+
60
+ query