@jd-data-limited/easy-fm 4.0.11 → 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.
Files changed (36) hide show
  1. package/package.json +1 -1
  2. package/src/bin/cli.d.ts +2 -0
  3. package/src/bin/cli.js +17 -0
  4. package/src/bin/generateTypes.d.ts +1 -0
  5. package/src/bin/generateTypes.js +161 -0
  6. package/src/bin/generateTypes.ts +10 -7
  7. package/src/index.d.ts +3 -3
  8. package/src/index.js +2 -2
  9. package/src/index.ts +4 -4
  10. package/src/layouts/recordFieldsMap.d.ts +2 -2
  11. package/src/layouts/recordFieldsMap.ts +2 -2
  12. package/src/records/field.d.ts +16 -10
  13. package/src/records/field.js +43 -59
  14. package/src/records/{fieldBase.ts → field.ts} +4 -11
  15. package/src/records/getOperations/recordGetOperation.d.ts +5 -1
  16. package/src/records/getOperations/recordGetOperation.js +22 -4
  17. package/src/records/getOperations/recordGetOperation.ts +26 -4
  18. package/src/records/layoutRecord.d.ts +3 -3
  19. package/src/records/layoutRecord.js +1 -1
  20. package/src/records/layoutRecord.ts +6 -6
  21. package/src/records/portalRecord.d.ts +2 -2
  22. package/src/records/portalRecord.ts +2 -2
  23. package/src/records/recordBase.d.ts +2 -2
  24. package/src/records/recordBase.js +5 -5
  25. package/src/records/recordBase.ts +7 -6
  26. package/src/utils/query.d.ts +4 -5
  27. package/src/utils/query.js +11 -4
  28. package/src/utils/query.ts +14 -7
  29. package/src/records/fieldBase.d.ts +0 -30
  30. package/src/records/fieldBase.js +0 -155
  31. package/src/records/getOperations/find.d.ts +0 -15
  32. package/src/records/getOperations/find.js +0 -80
  33. package/src/records/getOperations/recordGetRange.d.ts +0 -13
  34. package/src/records/getOperations/recordGetRange.js +0 -88
  35. package/src/records/portalRecordBase.d.ts +0 -4
  36. package/src/records/portalRecordBase.js +0 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jd-data-limited/easy-fm",
3
- "version": "4.0.11",
3
+ "version": "4.0.12",
4
4
  "dependencies": {
5
5
  "follow-redirects": "^1.15.5",
6
6
  "@types/follow-redirects": "^1.14.4",
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/src/bin/cli.js ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Copyright (c) 2024. See LICENSE file for more information
4
+ */
5
+ import { Command } from "commander";
6
+ import { generateTypesCLI } from "./generateTypes.js";
7
+ const program = new Command();
8
+ program
9
+ .name("easyfm")
10
+ .description("A NodeJS wrapper for the FileMaker Data CLI");
11
+ program
12
+ .command("generate-types")
13
+ .description("Automatically generate layout interfaces from your database")
14
+ .action(() => {
15
+ generateTypesCLI();
16
+ });
17
+ program.parse(process.argv);
@@ -0,0 +1 @@
1
+ export declare function generateTypesCLI(): Promise<void>;
@@ -0,0 +1,161 @@
1
+ /*
2
+ * Copyright (c) 2024. See LICENSE file for more information
3
+ */
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";
8
+ function sub(str) {
9
+ return str
10
+ .replace(/[^a-zA-Z0-9_]/g, "") // Remove non-alphanumeric characters
11
+ .replace(/^[0-9]+/g, ''); // Removing leading numbers
12
+ }
13
+ function generateFieldType(field) {
14
+ switch (field.result) {
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>";
27
+ }
28
+ }
29
+ function processFieldsChunk(fields) {
30
+ let existing_fields = [];
31
+ fields = fields.filter(field => {
32
+ if (existing_fields.includes(field.name))
33
+ return false;
34
+ existing_fields.push(field.name);
35
+ return true;
36
+ });
37
+ return fields.map(field => {
38
+ return JSON.stringify(field.name) + ": " + generateFieldType(field);
39
+ });
40
+ }
41
+ function camelToSnakeCase(str) {
42
+ return str.replace(/([a-z0-9])([A-Z])/g, '$1_$2').toLowerCase();
43
+ }
44
+ function safeStringInjection(str, ...args) {
45
+ let result = "";
46
+ for (let i = 0; i < str.length; i++) {
47
+ result += str[i];
48
+ if (i < args.length) {
49
+ result += JSON.stringify(args[i].toString());
50
+ }
51
+ }
52
+ return result;
53
+ }
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://");
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: []
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");
161
+ }
@@ -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 "FieldBase<string>"
19
+ return "Field<string>"
20
20
  case ApiFieldResultTypes.CONTAINER:
21
- return "FieldBase<Container>"
21
+ return "Field<Container>"
22
22
  case ApiFieldResultTypes.DATE:
23
- return "FieldBase<Date>"
23
+ return "Field<Date>"
24
24
  case ApiFieldResultTypes.NUMBER:
25
- return "FieldBase<number>"
25
+ return "Field<number>"
26
26
  case ApiFieldResultTypes.TIME:
27
- return "FieldBase<Date>"
27
+ return "Field<Date>"
28
28
  case ApiFieldResultTypes.TIMESTAMP:
29
- return "FieldBase<Date>"
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 = sub(layout.name) + "LayoutInterface"
147
+ let interfaceName = substituteName + "LayoutInterface"
145
148
  layoutInterfaces.set(layout.name, {
146
149
  interfaceName,
147
150
  interface: `export interface ${interfaceName} extends LayoutInterface {
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 { Container, FieldBase } from "./records/fieldBase.js";
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, FieldBase, RecordGetOperation, LayoutRecordManager, Container, PickPortals, FindRequest, query, queryEscape };
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, FieldBase, RecordGetOperation, LayoutRecordManager, query, queryEscape };
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 {Container, FieldBase} from "./records/fieldBase.js";
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
- FieldBase,
35
+ Field,
36
36
  RecordGetOperation,
37
37
  LayoutRecordManager,
38
38
  Container,
39
39
  PickPortals,
40
- FindRequest,
40
+ FindRequestRaw,
41
41
  query,
42
42
  queryEscape
43
43
  }
@@ -1,4 +1,4 @@
1
- import { FieldBase, FieldValue } from "../records/fieldBase.js";
1
+ import { Field, FieldValue } from "../records/field.js";
2
2
  export interface RecordFieldsMap {
3
- [fieldName: string]: FieldBase<FieldValue>;
3
+ [fieldName: string]: Field<FieldValue>;
4
4
  }
@@ -2,8 +2,8 @@
2
2
  * Copyright (c) 2023-2024. See LICENSE file for more information
3
3
  */
4
4
 
5
- import {FieldBase, FieldValue} from "../records/fieldBase.js";
5
+ import {Field, FieldValue} from "../records/field.js";
6
6
 
7
7
  export interface RecordFieldsMap {
8
- [fieldName: string]: FieldBase<FieldValue>
8
+ [fieldName: string]: Field<FieldValue>
9
9
  }
@@ -1,18 +1,24 @@
1
- /// <reference types="node" />
2
- /// <reference types="node" />
3
- import { RecordBase } from "./recordBase.js";
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ /// <reference types="node" resolution-mode="require"/>
4
3
  import * as http from "http";
5
- import { ContainerBufferResult, DOWNLOAD_MODES } from "../types.js";
4
+ import { ContainerBufferResult, DownloadModes, RecordTypes } from "../types.js";
6
5
  import { ApiFieldMetadata } from "../models/apiResults.js";
7
- export type FieldValue = string | number | Date | Container;
6
+ import { LayoutBase } from "../layouts/layoutBase.js";
7
+ import { Moment } from "moment";
8
+ export type FieldValue = string | number | Moment | Container;
8
9
  export type Container = null;
10
+ export type Parentable = {
11
+ layout: LayoutBase;
12
+ type: RecordTypes;
13
+ endpoint: string;
14
+ };
9
15
  export declare class Field<T extends FieldValue> {
10
- record: RecordBase<any>;
16
+ parent: Parentable;
11
17
  id: string;
12
18
  protected _value: T;
13
19
  edited: boolean;
14
- constructor(record: any, id: any, contents: any);
15
- set(content: string | number | Date | undefined | null): void;
20
+ constructor(record: Parentable, id: string, contents: T);
21
+ set(content: T): void;
16
22
  get metadata(): ApiFieldMetadata;
17
23
  get value(): T;
18
24
  set value(value: T);
@@ -20,6 +26,6 @@ export declare class Field<T extends FieldValue> {
20
26
  get date(): Date;
21
27
  get number(): number;
22
28
  upload(buffer: Buffer, filename: string, mime: string): Promise<void>;
23
- download(mode: DOWNLOAD_MODES.Stream): Promise<http.IncomingMessage>;
24
- download(mode: DOWNLOAD_MODES.Buffer): Promise<ContainerBufferResult>;
29
+ download(mode: DownloadModes.Stream): Promise<http.IncomingMessage>;
30
+ download(mode: DownloadModes.Buffer): Promise<ContainerBufferResult>;
25
31
  }
@@ -1,22 +1,15 @@
1
1
  /*
2
- * Copyright (c) 2023. See LICENSE file for more information
2
+ * Copyright (c) 2023-2024. See LICENSE file for more information
3
3
  */
4
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
5
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
6
- return new (P || (P = Promise))(function (resolve, reject) {
7
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
8
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
9
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
10
- step((generator = generator.apply(thisArg, _arguments || [])).next());
11
- });
12
- };
13
- import { RecordTypes } from "./recordBase.js";
14
- import { File, FormData } from "node-fetch";
15
- import { DOWNLOAD_MODES } from "../types.js";
4
+ import { DownloadModes, RecordTypes } from "../types.js";
16
5
  import { ApiFieldDisplayTypes, ApiFieldResultTypes, ApiFieldTypes } from "../models/apiResults.js";
17
6
  export class Field {
7
+ parent;
8
+ id;
9
+ _value;
10
+ edited;
18
11
  constructor(record, id, contents) {
19
- this.record = record;
12
+ this.parent = record;
20
13
  this.id = id;
21
14
  this._value = contents;
22
15
  this.edited = false;
@@ -24,12 +17,6 @@ export class Field {
24
17
  set(content) {
25
18
  if (this.metadata.result === "container")
26
19
  throw "Cannot set container value using set(). Use upload() instead.";
27
- if ((this.metadata.result === "timeStamp" ||
28
- this.metadata.result === "date" ||
29
- this.metadata.result === "time")
30
- && !(content instanceof Date) && !!content) {
31
- throw "Value was not an instance of Date: " + content;
32
- }
33
20
  // @ts-ignore
34
21
  if (!content)
35
22
  this._value = "";
@@ -39,7 +26,7 @@ export class Field {
39
26
  this.edited = true;
40
27
  }
41
28
  get metadata() {
42
- if (!this.record.layout.metadata) {
29
+ if (!this.parent.layout.metadata) {
43
30
  // Default to a regular text field
44
31
  return {
45
32
  name: this.id.toString(),
@@ -58,9 +45,9 @@ export class Field {
58
45
  valueList: ""
59
46
  };
60
47
  }
61
- if (this.record.type === RecordTypes.PORTAL) {
48
+ if (this.parent.type === RecordTypes.PORTAL) {
62
49
  // @ts-ignore
63
- return this.record.layout.metadata.portalMetaData[this.record.portal.name || "portal not attached"].find(i => i.name === this.id) || {
50
+ return this.parent.layout.metadata.portalMetaData[this.parent.portal.name || "portal not attached"].find(i => i.name === this.id) || {
64
51
  name: this.id.toString(),
65
52
  type: 'normal',
66
53
  displayType: 'editText',
@@ -78,7 +65,7 @@ export class Field {
78
65
  };
79
66
  }
80
67
  else {
81
- return this.record.layout.metadata.fieldMetaData.find(i => i.name === this.id) || {
68
+ return this.parent.layout.metadata.fieldMetaData.find(i => i.name === this.id) || {
82
69
  name: this.id.toString(),
83
70
  type: ApiFieldTypes.NORMAL,
84
71
  displayType: ApiFieldDisplayTypes.EDIT_TEXT,
@@ -121,43 +108,40 @@ export class Field {
121
108
  }
122
109
  throw "Field value is not a number";
123
110
  }
124
- upload(buffer, filename, mime) {
125
- return __awaiter(this, void 0, void 0, function* () {
126
- let trace = new Error();
127
- if (this.metadata.result !== "container")
128
- throw "Cannot upload a file to the field; " + this.id + " (not a container field)";
129
- let form = new FormData();
130
- form.append("upload", new File([buffer], filename, { type: mime }));
131
- yield this.record.layout.database.apiRequest(`${this.record.endpoint}/containers/${this.id}/1`, {
132
- method: "POST",
133
- // @ts-ignore
134
- headers: { "content-type": "multipart/form-data" },
135
- body: form
136
- });
111
+ async upload(buffer, filename, mime) {
112
+ let trace = new Error();
113
+ if (this.metadata.result !== "container")
114
+ throw "Cannot upload a file to the field; " + this.id + " (not a container field)";
115
+ let form = new FormData();
116
+ form.append("upload", new File([buffer], filename, { type: mime }));
117
+ let res = await this.parent.layout.database.apiRequestRaw(`${this.parent.endpoint}/containers/${this.id}/1`, {
118
+ method: "POST",
119
+ // @ts-ignore
120
+ body: form
137
121
  });
122
+ if (!res.ok)
123
+ throw new Error(`Upload failed with HTTP error: ${res.status} (${res.statusText})`);
138
124
  }
139
- download(mode = DOWNLOAD_MODES.Stream) {
140
- return __awaiter(this, void 0, void 0, function* () {
141
- if (this.metadata.result !== ApiFieldResultTypes.CONTAINER)
142
- throw new Error("Cannot perform download() on a non-container field");
143
- let stream = yield this.record.layout.database.streamURL(this.string);
144
- if (mode === DOWNLOAD_MODES.Stream) {
145
- return stream;
146
- }
147
- let body = [];
148
- return new Promise((resolve, reject) => {
149
- stream.on("data", chunk => {
150
- body.push(chunk);
151
- });
152
- stream.on("error", (e) => {
153
- reject(e);
154
- });
155
- stream.on("end", () => {
156
- resolve({
157
- buffer: Buffer.concat(body),
158
- mime: stream.headers["content-type"],
159
- request: stream
160
- });
125
+ async download(mode = DownloadModes.Stream) {
126
+ if (this.metadata.result !== ApiFieldResultTypes.CONTAINER)
127
+ throw new Error("Cannot perform download() on a non-container field");
128
+ let stream = await this.parent.layout.database.streamURL(this.string);
129
+ if (mode === DownloadModes.Stream) {
130
+ return stream;
131
+ }
132
+ let body = [];
133
+ return new Promise((resolve, reject) => {
134
+ stream.on("data", chunk => {
135
+ body.push(chunk);
136
+ });
137
+ stream.on("error", (e) => {
138
+ reject(e);
139
+ });
140
+ stream.on("end", () => {
141
+ resolve({
142
+ buffer: Buffer.concat(body),
143
+ mime: stream.headers["content-type"],
144
+ request: stream
161
145
  });
162
146
  });
163
147
  });
@@ -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 | Date | Container
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 FieldBase<T extends FieldValue> {
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: string | number | Date | undefined | null) {
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: FindRequest;
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
- return this.queries.map(query => {
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][FindRequestSymbol])
30
- out[key] = query.req[key][FindRequestSymbol];
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() {