@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.
- package/package.json +1 -1
- package/src/bin/cli.d.ts +2 -0
- package/src/bin/cli.js +17 -0
- package/src/bin/generateTypes.d.ts +1 -0
- package/src/bin/generateTypes.js +161 -0
- package/src/bin/generateTypes.ts +10 -7
- 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/field.d.ts +16 -10
- package/src/records/field.js +43 -59
- 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/utils/query.d.ts +4 -5
- package/src/utils/query.js +11 -4
- package/src/utils/query.ts +14 -7
- package/src/records/fieldBase.d.ts +0 -30
- package/src/records/fieldBase.js +0 -155
- package/src/records/getOperations/find.d.ts +0 -15
- package/src/records/getOperations/find.js +0 -80
- package/src/records/getOperations/recordGetRange.d.ts +0 -13
- package/src/records/getOperations/recordGetRange.js +0 -88
- package/src/records/portalRecordBase.d.ts +0 -4
- package/src/records/portalRecordBase.js +0 -6
package/package.json
CHANGED
package/src/bin/cli.d.ts
ADDED
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
|
+
}
|
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 {
|
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
|
}
|
package/src/records/field.d.ts
CHANGED
|
@@ -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,
|
|
4
|
+
import { ContainerBufferResult, DownloadModes, RecordTypes } from "../types.js";
|
|
6
5
|
import { ApiFieldMetadata } from "../models/apiResults.js";
|
|
7
|
-
|
|
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
|
-
|
|
16
|
+
parent: Parentable;
|
|
11
17
|
id: string;
|
|
12
18
|
protected _value: T;
|
|
13
19
|
edited: boolean;
|
|
14
|
-
constructor(record:
|
|
15
|
-
set(content:
|
|
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:
|
|
24
|
-
download(mode:
|
|
29
|
+
download(mode: DownloadModes.Stream): Promise<http.IncomingMessage>;
|
|
30
|
+
download(mode: DownloadModes.Buffer): Promise<ContainerBufferResult>;
|
|
25
31
|
}
|
package/src/records/field.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
48
|
+
if (this.parent.type === RecordTypes.PORTAL) {
|
|
62
49
|
// @ts-ignore
|
|
63
|
-
return this.
|
|
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.
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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 =
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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 |
|
|
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() {
|