@jd-data-limited/easy-fm 4.1.6 → 4.1.9
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/dist/bin/cli.js +17 -0
- package/dist/bin/generateTypes.js +161 -0
- package/{src → dist}/connection/FMHost.js +3 -2
- package/{src → dist}/connection/database.d.ts +0 -1
- package/{src → dist}/connection/database.js +22 -39
- package/dist/index.d.ts +18 -0
- package/dist/index.js +16 -0
- package/{src → dist}/records/field.d.ts +0 -1
- package/{src → dist}/records/getOperations/recordGetOperation.d.ts +6 -2
- package/{src → dist}/records/getOperations/recordGetOperation.js +9 -5
- package/{src → dist}/records/layoutRecord.d.ts +1 -1
- package/{src → dist}/records/layoutRecord.js +5 -6
- package/{src → dist}/records/recordBase.d.ts +0 -1
- package/{src → dist}/types.d.ts +0 -2
- package/package.json +18 -10
- package/src/FMError.ts +0 -1033
- package/src/bin/cli.js +0 -17
- package/src/bin/cli.ts +0 -22
- package/src/bin/generateTypes.d.ts +0 -1
- package/src/bin/generateTypes.js +0 -161
- package/src/bin/generateTypes.ts +0 -189
- package/src/connection/FMHost.ts +0 -123
- package/src/connection/HostBase.ts +0 -18
- package/src/connection/database.ts +0 -257
- package/src/connection/databaseBase.ts +0 -19
- package/src/connection/generateAuthorizationHeaders.ts +0 -32
- package/src/connection/requestHoldable.d.ts +0 -10
- package/src/connection/requestHoldable.js +0 -44
- package/src/databaseStructure.ts +0 -10
- package/src/index.d.ts +0 -18
- package/src/index.js +0 -18
- package/src/index.ts +0 -47
- package/src/layouts/layout.ts +0 -66
- package/src/layouts/layoutBase.ts +0 -16
- package/src/layouts/layoutInterface.ts +0 -13
- package/src/layouts/layoutRecordManager.ts +0 -66
- package/src/layouts/recordFieldsMap.ts +0 -7
- package/src/models/apiResults.ts +0 -110
- package/src/records/field.ts +0 -235
- package/src/records/getOperations/recordGetOperation.ts +0 -268
- package/src/records/layoutRecord.ts +0 -247
- package/src/records/layoutRecordBase.ts +0 -10
- package/src/records/portal.ts +0 -43
- package/src/records/portalBase.ts +0 -14
- package/src/records/portalRecord.ts +0 -52
- package/src/records/recordBase.ts +0 -91
- package/src/types.ts +0 -162
- package/src/utils/query.ts +0 -59
- /package/{src → dist}/FMError.d.ts +0 -0
- /package/{src → dist}/FMError.js +0 -0
- /package/{src → dist}/bin/cli.d.ts +0 -0
- /package/{src/bin/dist → dist}/bin/generateTypes.d.ts +0 -0
- /package/{src → dist}/connection/FMHost.d.ts +0 -0
- /package/{src → dist}/connection/HostBase.d.ts +0 -0
- /package/{src → dist}/connection/HostBase.js +0 -0
- /package/{src → dist}/connection/databaseBase.d.ts +0 -0
- /package/{src → dist}/connection/databaseBase.js +0 -0
- /package/{src → dist}/connection/generateAuthorizationHeaders.d.ts +0 -0
- /package/{src → dist}/connection/generateAuthorizationHeaders.js +0 -0
- /package/{src → dist}/databaseStructure.d.ts +0 -0
- /package/{src → dist}/databaseStructure.js +0 -0
- /package/{src → dist}/layouts/layout.d.ts +0 -0
- /package/{src → dist}/layouts/layout.js +0 -0
- /package/{src → dist}/layouts/layoutBase.d.ts +0 -0
- /package/{src → dist}/layouts/layoutBase.js +0 -0
- /package/{src → dist}/layouts/layoutInterface.d.ts +0 -0
- /package/{src → dist}/layouts/layoutInterface.js +0 -0
- /package/{src → dist}/layouts/layoutRecordManager.d.ts +0 -0
- /package/{src → dist}/layouts/layoutRecordManager.js +0 -0
- /package/{src → dist}/layouts/recordFieldsMap.d.ts +0 -0
- /package/{src → dist}/layouts/recordFieldsMap.js +0 -0
- /package/{src → dist}/models/apiResults.d.ts +0 -0
- /package/{src → dist}/models/apiResults.js +0 -0
- /package/{src → dist}/records/field.js +0 -0
- /package/{src → dist}/records/layoutRecordBase.d.ts +0 -0
- /package/{src → dist}/records/layoutRecordBase.js +0 -0
- /package/{src → dist}/records/portal.d.ts +0 -0
- /package/{src → dist}/records/portal.js +0 -0
- /package/{src → dist}/records/portalBase.d.ts +0 -0
- /package/{src → dist}/records/portalBase.js +0 -0
- /package/{src → dist}/records/portalRecord.d.ts +0 -0
- /package/{src → dist}/records/portalRecord.js +0 -0
- /package/{src → dist}/records/recordBase.js +0 -0
- /package/{src → dist}/types.js +0 -0
- /package/{src → dist}/utils/query.d.ts +0 -0
- /package/{src → dist}/utils/query.js +0 -0
package/dist/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(async () => {
|
|
15
|
+
await generateTypesCLI();
|
|
16
|
+
});
|
|
17
|
+
program.parse(process.argv);
|
|
@@ -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 'node: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
|
+
const existingFields = [];
|
|
31
|
+
fields = fields.filter(field => {
|
|
32
|
+
if (existingFields.includes(field.name))
|
|
33
|
+
return false;
|
|
34
|
+
existingFields.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
|
+
const 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
|
+
const layoutInterfaces = new Map();
|
|
118
|
+
for (const layout of layouts) {
|
|
119
|
+
const substituteName = sub(layout.name);
|
|
120
|
+
if (!substituteName)
|
|
121
|
+
continue;
|
|
122
|
+
console.log(substituteName);
|
|
123
|
+
const metadata = await layout.getLayoutMeta();
|
|
124
|
+
const fields = processFieldsChunk(metadata.fieldMetaData);
|
|
125
|
+
const portals = metadata.portalMetaData;
|
|
126
|
+
const portalsProcessed = Object.keys(portals).map(portalName => {
|
|
127
|
+
return `${JSON.stringify(portalName)}: Portal<{${processFieldsChunk(portals[portalName]).join('\n')}}>`;
|
|
128
|
+
});
|
|
129
|
+
const 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
|
+
const layoutInterfaceLinks = [];
|
|
139
|
+
for (const 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 (const 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
|
+
}
|
|
@@ -17,8 +17,9 @@ export default class FMHost {
|
|
|
17
17
|
constructor(_hostname, timezoneOffset = (moment) => 0 - (new Date()).getTimezoneOffset(), verify = true) {
|
|
18
18
|
if (!(/^https?:\/\//).test(_hostname))
|
|
19
19
|
throw new Error('hostname MUST begin with either http:// or https://');
|
|
20
|
-
|
|
21
|
-
this.
|
|
20
|
+
const url = new URL(_hostname);
|
|
21
|
+
this.protocol = url.protocol;
|
|
22
|
+
this.hostname = url.hostname;
|
|
22
23
|
this.timezoneOffsetFunc = timezoneOffset;
|
|
23
24
|
this.verify = verify;
|
|
24
25
|
}
|
|
@@ -78,27 +78,25 @@ export class Database extends EventEmitter {
|
|
|
78
78
|
this._token = (this.connection_details.credentials).token;
|
|
79
79
|
return this.token;
|
|
80
80
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return this.generateExternalSourceLogin(_i);
|
|
91
|
-
})
|
|
81
|
+
const url = new URL(`${this.endpoint}/sessions`);
|
|
82
|
+
url.hostname = this.host.hostname;
|
|
83
|
+
const res = await fetch(url, {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
headers: generateAuthorizationHeaders(this.connection_details.credentials),
|
|
86
|
+
body: JSON.stringify({
|
|
87
|
+
fmDataSource: this.connection_details.externalSources.map(i => {
|
|
88
|
+
const _i = i;
|
|
89
|
+
return this.generateExternalSourceLogin(_i);
|
|
92
90
|
})
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
91
|
+
})
|
|
92
|
+
});
|
|
93
|
+
const _res = (await res.json());
|
|
94
|
+
if (res.status === 200) {
|
|
95
|
+
this._token = res.headers.get('x-fm-data-access-token') ?? '';
|
|
96
|
+
return this._token;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
throw new FMError(_res.messages[0].code, _res.status, res);
|
|
102
100
|
}
|
|
103
101
|
}
|
|
104
102
|
get token() {
|
|
@@ -118,35 +116,21 @@ export class Database extends EventEmitter {
|
|
|
118
116
|
? url.toString()
|
|
119
117
|
: typeof url === 'string' ? url : url.url}`);
|
|
120
118
|
}
|
|
121
|
-
const
|
|
119
|
+
const urlParsed = (url instanceof URL
|
|
122
120
|
? url
|
|
123
121
|
: typeof url === 'string'
|
|
124
122
|
? new URL(url)
|
|
125
|
-
: new URL(url.url))
|
|
123
|
+
: new URL(url.url));
|
|
124
|
+
const reqIsToDBHost = urlParsed.hostname === this.host.hostname && urlParsed.pathname.startsWith("/fmi/data");
|
|
126
125
|
if (reqIsToDBHost && this.token === '')
|
|
127
126
|
await this.login(true);
|
|
128
127
|
if (!options.headers)
|
|
129
128
|
options.headers = {};
|
|
130
129
|
if (reqIsToDBHost)
|
|
131
130
|
options.headers.authorization = 'Bearer ' + this._token;
|
|
132
|
-
// options.redirect = "manual"
|
|
133
|
-
// options.headers.cookies = this._generateCookieHeader()
|
|
134
|
-
// console.log(options.headers.cookies)
|
|
135
|
-
// options.rejectUnauthorized = this.host.verify
|
|
136
131
|
const _fetch = options.useCookieJar
|
|
137
132
|
? await fetchWithCookies(this.cookies, url, options)
|
|
138
133
|
: await fetch(url, options);
|
|
139
|
-
// if (_fetch.headers.get('set-cookie')) {
|
|
140
|
-
// console.log(_fetch.headers.get('set-cookie'))
|
|
141
|
-
// for (const cookie of (_fetch.headers.get("Set-Cookie") ?? "").split("; ")) {
|
|
142
|
-
// const cookieSplit = cookie.split('=')
|
|
143
|
-
// console.log("COOKIE SPLIT:", cookieSplit)
|
|
144
|
-
// if (cookieSplit[1]) this.cookies[cookieSplit[0]] = cookieSplit[1]
|
|
145
|
-
// }
|
|
146
|
-
// }
|
|
147
|
-
// console.log(this.cookies, _fetch.status)
|
|
148
|
-
// // console.log(this, this.cookies, url)
|
|
149
|
-
// console.trace()
|
|
150
134
|
if (!_fetch.ok && (!options.retries || options.retries > 0)) {
|
|
151
135
|
if (this.debug) {
|
|
152
136
|
console.log(`EASYFM DEBUG: RE-ATTEMPTING REQUEST (${_fetch.status}) ${url instanceof URL
|
|
@@ -186,7 +170,6 @@ export class Database extends EventEmitter {
|
|
|
186
170
|
*/
|
|
187
171
|
async listLayouts(page = 0) {
|
|
188
172
|
const req = await this._apiRequestJSON(`${this.endpoint}/layouts?page=${encodeURIComponent(page)}`);
|
|
189
|
-
console.log(req);
|
|
190
173
|
if (!req.response)
|
|
191
174
|
throw new FMError(req.messages[0].code, req.httpStatus, req.messages[0].message);
|
|
192
175
|
const cycleLayoutNames = (layouts) => {
|
|
@@ -213,6 +196,6 @@ export class Database extends EventEmitter {
|
|
|
213
196
|
this.#layoutCache.clear();
|
|
214
197
|
}
|
|
215
198
|
script(name, parameter = '') {
|
|
216
|
-
return
|
|
199
|
+
return { name, parameter };
|
|
217
200
|
}
|
|
218
201
|
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import FMHost from './connection/FMHost.js';
|
|
2
|
+
import type * as TYPES from './types.js';
|
|
3
|
+
export { FMError } from './FMError.js';
|
|
4
|
+
export { type PickPortals } from './types.js';
|
|
5
|
+
export { Database } from './connection/database.js';
|
|
6
|
+
export { Layout } from './layouts/layout.js';
|
|
7
|
+
export { RecordBase } from './records/recordBase.js';
|
|
8
|
+
export { LayoutRecord } from './records/layoutRecord.js';
|
|
9
|
+
export { type LayoutInterface, type PortalInterface } from './layouts/layoutInterface.js';
|
|
10
|
+
export { PortalRecord } from './records/portalRecord.js';
|
|
11
|
+
export { Portal } from './records/portal.js';
|
|
12
|
+
export { type FindRequest, type FindRequestRaw, RecordGetOperation } from './records/getOperations/recordGetOperation.js';
|
|
13
|
+
export { LayoutRecordManager } from './layouts/layoutRecordManager.js';
|
|
14
|
+
export { type RecordFieldsMap } from './layouts/recordFieldsMap.js';
|
|
15
|
+
export { asDate, asTime, asTimestamp, query, queryEscape } from './utils/query.js';
|
|
16
|
+
export { type Container, Field } from './records/field.js';
|
|
17
|
+
export default FMHost;
|
|
18
|
+
export { type TYPES };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2024. See LICENSE file for more information
|
|
3
|
+
*/
|
|
4
|
+
import FMHost from './connection/FMHost.js';
|
|
5
|
+
export { FMError } from './FMError.js';
|
|
6
|
+
export { Database } from './connection/database.js';
|
|
7
|
+
export { Layout } from './layouts/layout.js';
|
|
8
|
+
export { RecordBase } from './records/recordBase.js';
|
|
9
|
+
export { LayoutRecord } from './records/layoutRecord.js';
|
|
10
|
+
export { PortalRecord } from './records/portalRecord.js';
|
|
11
|
+
export { Portal } from './records/portal.js';
|
|
12
|
+
export { RecordGetOperation } from './records/getOperations/recordGetOperation.js';
|
|
13
|
+
export { LayoutRecordManager } from './layouts/layoutRecordManager.js';
|
|
14
|
+
export { asDate, asTime, asTimestamp, query, queryEscape } from './utils/query.js';
|
|
15
|
+
export { Field } from './records/field.js';
|
|
16
|
+
export default FMHost;
|
|
@@ -17,6 +17,10 @@ type PortalData<T extends LayoutInterface> = {
|
|
|
17
17
|
};
|
|
18
18
|
export interface GetOperationOptions<T extends LayoutInterface> {
|
|
19
19
|
portals: Partial<PortalData<T>>;
|
|
20
|
+
requests?: Array<{
|
|
21
|
+
req: FindRequest;
|
|
22
|
+
omit?: boolean;
|
|
23
|
+
}>;
|
|
20
24
|
limit?: number;
|
|
21
25
|
offset?: number;
|
|
22
26
|
}
|
|
@@ -30,9 +34,9 @@ export declare class RecordGetOperation<T extends LayoutInterface, OPTIONS exten
|
|
|
30
34
|
}>;
|
|
31
35
|
protected portals: Partial<PortalData<T>>;
|
|
32
36
|
protected offset: number;
|
|
33
|
-
protected
|
|
37
|
+
protected requests: Array<{
|
|
34
38
|
req: FindRequestRaw;
|
|
35
|
-
omit
|
|
39
|
+
omit?: boolean;
|
|
36
40
|
}>;
|
|
37
41
|
constructor(layout: LayoutBase, options: OPTIONS);
|
|
38
42
|
get isFindRequest(): boolean;
|
|
@@ -11,19 +11,23 @@ export class RecordGetOperation {
|
|
|
11
11
|
sortData = [];
|
|
12
12
|
portals;
|
|
13
13
|
offset = 1;
|
|
14
|
-
|
|
14
|
+
requests = [];
|
|
15
15
|
constructor(layout, options) {
|
|
16
16
|
this.layout = layout;
|
|
17
17
|
this.sortData = [];
|
|
18
18
|
this.portals = options.portals;
|
|
19
19
|
this.offset = options.offset ?? 1; // Offset refers to the starting record. offset 1 is the same as no offset.
|
|
20
20
|
this.limit = options.limit ?? 100;
|
|
21
|
+
if (options.requests) {
|
|
22
|
+
for (const req of options.requests)
|
|
23
|
+
this.addRequest(req.req, req.omit ?? false);
|
|
24
|
+
}
|
|
21
25
|
}
|
|
22
26
|
get isFindRequest() {
|
|
23
|
-
return this.
|
|
27
|
+
return this.requests.length !== 0;
|
|
24
28
|
}
|
|
25
29
|
formatQueries() {
|
|
26
|
-
const test = this.
|
|
30
|
+
const test = this.requests.map(query => {
|
|
27
31
|
const out = {};
|
|
28
32
|
for (const key of Object.keys(query.req)) {
|
|
29
33
|
if (query.req[key])
|
|
@@ -58,7 +62,7 @@ export class RecordGetOperation {
|
|
|
58
62
|
params['script.prerequest'] = this.scriptData.prerequest.name;
|
|
59
63
|
if (this.scriptData.prerequest?.parameter)
|
|
60
64
|
params['script.prerequest.param'] = this.scriptData.prerequest.parameter;
|
|
61
|
-
if (this.
|
|
65
|
+
if (this.requests.length !== 0)
|
|
62
66
|
params.query = this.formatQueries();
|
|
63
67
|
const portals = Object.keys(this.portals);
|
|
64
68
|
params.portal = portals;
|
|
@@ -146,7 +150,7 @@ export class RecordGetOperation {
|
|
|
146
150
|
* @return {Object} - The current object instance.
|
|
147
151
|
*/
|
|
148
152
|
addRequest(query, omit = false) {
|
|
149
|
-
this.
|
|
153
|
+
this.requests.push({ req: this.parseFindRequest(query), omit });
|
|
150
154
|
return this;
|
|
151
155
|
}
|
|
152
156
|
/**
|
|
@@ -32,5 +32,5 @@ export declare class LayoutRecord<LAYOUT extends LayoutInterface> extends Record
|
|
|
32
32
|
duplicate(): Promise<LayoutRecord<LAYOUT>>;
|
|
33
33
|
delete(): Promise<void>;
|
|
34
34
|
fieldsToObject(filter?: (a: Field<FieldValue>) => boolean): Omit<ApiRowDataDef, 'portalData'>;
|
|
35
|
-
toObject(filter?: (a: Field<FieldValue>) => any, portalFilter?: (a: Portal<any>) => any, portalRowFilter?: (a: PortalRecord<
|
|
35
|
+
toObject(filter?: (a: Field<FieldValue>) => any, portalFilter?: (a: Portal<any>) => any, portalRowFilter?: (a: PortalRecord<any>) => any, portalFieldFilter?: (a: Field<FieldValue>) => any): ApiRowDataDef;
|
|
36
36
|
}
|
|
@@ -5,7 +5,7 @@ import { RecordBase } from './recordBase.js';
|
|
|
5
5
|
import { PortalRecord } from './portalRecord.js';
|
|
6
6
|
import { Portal } from './portal.js';
|
|
7
7
|
import { FMError } from '../FMError.js';
|
|
8
|
-
import
|
|
8
|
+
import moment from 'moment';
|
|
9
9
|
export class LayoutRecord extends RecordBase {
|
|
10
10
|
portals = {};
|
|
11
11
|
portalsToInclude;
|
|
@@ -168,12 +168,12 @@ export class LayoutRecord extends RecordBase {
|
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
fieldsToObject(filter = (a) => a.edited) {
|
|
171
|
-
const
|
|
171
|
+
const fieldsProcessed = {};
|
|
172
172
|
let field;
|
|
173
173
|
for (field of this.fieldsArray.filter(field => filter(field))) {
|
|
174
174
|
let value = field.value;
|
|
175
175
|
if (value instanceof Date) {
|
|
176
|
-
let _value = moment
|
|
176
|
+
let _value = moment(value);
|
|
177
177
|
_value = _value
|
|
178
178
|
.utcOffset(this.layout.database.host.timezoneOffsetFunc(_value));
|
|
179
179
|
switch (field.metadata.result) {
|
|
@@ -187,12 +187,12 @@ export class LayoutRecord extends RecordBase {
|
|
|
187
187
|
value = _value.format(this.layout.database.host.timeStampFormat);
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
|
-
|
|
190
|
+
fieldsProcessed[field.id] = value;
|
|
191
191
|
}
|
|
192
192
|
const obj = {
|
|
193
193
|
recordId: this.recordId.toString(),
|
|
194
194
|
modId: this.modId.toString(),
|
|
195
|
-
fieldData:
|
|
195
|
+
fieldData: fieldsProcessed
|
|
196
196
|
};
|
|
197
197
|
return obj;
|
|
198
198
|
}
|
|
@@ -206,7 +206,6 @@ export class LayoutRecord extends RecordBase {
|
|
|
206
206
|
if (portals) {
|
|
207
207
|
obj.portalData = {};
|
|
208
208
|
for (const portal of portals) {
|
|
209
|
-
// @ts-expect-error
|
|
210
209
|
obj.portalData[portal.name] = portal.records.filter(a => portalRowFilter(a)).map(record => {
|
|
211
210
|
return record.toObject(portalFieldFilter);
|
|
212
211
|
});
|
package/{src → dist}/types.d.ts
RENAMED
package/package.json
CHANGED
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jd-data-limited/easy-fm",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.9",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"@types/follow-redirects": "^1.14.4",
|
|
6
|
+
"@types/node": "^22.7.7",
|
|
7
|
+
"commander": "^14.0.0",
|
|
6
8
|
"follow-redirects": "^1.15.5",
|
|
7
9
|
"moment": "^2.29.4",
|
|
8
10
|
"node-fetch": "^3.3.2",
|
|
9
11
|
"node-fetch-cookies": "^2.1.1"
|
|
10
12
|
},
|
|
11
13
|
"type": "module",
|
|
12
|
-
"main": "
|
|
14
|
+
"main": "dist/index.js",
|
|
13
15
|
"module": "es2022",
|
|
14
16
|
"homepage": "https://jd-data.com/project/easyfm/",
|
|
15
17
|
"url": "https://github.com/JD-Data-Limited/easy-fm",
|
|
16
18
|
"license": "SEE LICENSE IN LICENSE",
|
|
17
19
|
"description": "easy-fm is a Node.js module that allows you to interact with\r a [FileMaker database stored](https://www.claris.com/filemaker/) on\r a [FileMaker server](https://www.claris.com/filemaker/server/). This module interacts with your server using the\r [FileMaker Data API](https://help.claris.com/en/data-api-guide/content/index.html).",
|
|
18
20
|
"files": [
|
|
19
|
-
"./
|
|
21
|
+
"./dist/**/*"
|
|
20
22
|
],
|
|
21
23
|
"scripts": {
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
+
"build": "tsc",
|
|
25
|
+
"test": "npm run build && jest"
|
|
24
26
|
},
|
|
25
27
|
"repository": {
|
|
26
28
|
"type": "git",
|
|
@@ -42,10 +44,16 @@
|
|
|
42
44
|
"node": ">=20.9.0"
|
|
43
45
|
},
|
|
44
46
|
"devDependencies": {
|
|
47
|
+
"@babel/cli": "^7.25.7",
|
|
48
|
+
"@babel/core": "^7.25.8",
|
|
49
|
+
"@babel/preset-env": "^7.25.8",
|
|
50
|
+
"@babel/preset-typescript": "^7.25.7",
|
|
45
51
|
"@types/btoa": "^1.2.3",
|
|
46
52
|
"@types/events": "^3.0.0",
|
|
47
53
|
"@types/inquirer": "^9.0.7",
|
|
54
|
+
"@types/jest": "^29.5.13",
|
|
48
55
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
|
56
|
+
"babel-jest": "^29.7.0",
|
|
49
57
|
"dotenv": "^16.3.1",
|
|
50
58
|
"eslint": "^8.57.0",
|
|
51
59
|
"eslint-config-standard-with-typescript": "^43.0.1",
|
|
@@ -53,13 +61,13 @@
|
|
|
53
61
|
"eslint-plugin-n": "^16.6.2",
|
|
54
62
|
"eslint-plugin-promise": "^6.1.1",
|
|
55
63
|
"inquirer": "^9.2.13",
|
|
64
|
+
"jest": "^29.7.0",
|
|
65
|
+
"ts-jest": "^29.2.5",
|
|
56
66
|
"ts-morph": "^21.0.1",
|
|
57
|
-
"
|
|
58
|
-
"typescript": "^5.
|
|
59
|
-
"webpack": "^5.81.0",
|
|
60
|
-
"webpack-cli": "^5.0.2"
|
|
67
|
+
"tsx": "^4.19.4",
|
|
68
|
+
"typescript": "^5.6.3"
|
|
61
69
|
},
|
|
62
70
|
"bin": {
|
|
63
|
-
"easyfm": "
|
|
71
|
+
"easyfm": "dist/bin/cli.js"
|
|
64
72
|
}
|
|
65
73
|
}
|